commit e9986ef8c317369a85c4c6ec0d59c5c376407595
Author: MUQING <1966944300@qq.com>
Date: Sat Oct 21 22:07:35 2023 +0800
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..cd4ecd2
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Cloud_music
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..ae388c2
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..30f3112
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..568bea1
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..1814a74
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,83 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'maven-publish'
+}
+android {
+ namespace 'com.muqingbfq'
+ compileSdk 33
+ defaultConfig {
+ applicationId "com.muqingbfq"
+ minSdk 23
+ //noinspection OldTargetApi
+ targetSdk 31
+ versionCode 1
+ versionName "1.5.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ buildFeatures {
+ viewBinding true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ android.applicationVariants.configureEach {
+ variant ->
+ variant.outputs.configureEach {
+ //在这里修改apk文件名
+ outputFileName = "Cloud_music-${variant.name}-v${variant.versionName}.apk"
+ }
+ }
+ signingConfigs {
+ signConfig {
+ storeFile file('F:/muqing.jks')
+// 证书存放位置和名字
+ storePassword 'muqing153'
+// 密码
+ keyAlias 'muqing'
+// 别名
+ keyPassword 'muqing153'
+// 别名对应的密码
+ }
+ release {
+ v1SigningEnabled true //正式版时对应的签名规则 true使用 false不适用
+ v2SigningEnabled true
+ }
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.9.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.3.0'
+ implementation 'com.google.code.gson:gson:2.9.1'
+
+ implementation 'com.squareup.okhttp3:okhttp:4.11.0'
+ implementation 'com.github.bumptech.glide:glide:4.16.0'
+
+ api "com.github.cy745:EaseView:e11c3208a9"
+ implementation "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03"
+}
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ groupId = 'com.github.Moriafly'
+ artifactId = 'LyricViewX'
+ version = '1.4.0-alpha02'
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ffc2db0
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..63fe13d
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt b/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt
new file mode 100644
index 0000000..d0422ee
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/ILyricViewX.kt
@@ -0,0 +1,257 @@
+package com.dirror.lyricviewx
+
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.text.Layout
+import androidx.annotation.ColorInt
+import androidx.annotation.FloatRange
+import androidx.annotation.Px
+import java.io.File
+
+const val GRAVITY_CENTER = 0 // 居中
+const val GRAVITY_LEFT = 1 // 左
+const val GRAVITY_RIGHT = 2 // 右
+
+fun Int.toLayoutAlign(): Layout.Alignment {
+ return when (this) {
+ GRAVITY_LEFT -> Layout.Alignment.ALIGN_NORMAL
+ GRAVITY_CENTER -> Layout.Alignment.ALIGN_CENTER
+ GRAVITY_RIGHT -> Layout.Alignment.ALIGN_OPPOSITE
+ else -> Layout.Alignment.ALIGN_CENTER
+ }
+}
+
+/**
+ * LyricViewX 接口
+ * 从 LyricViewX 提取,方便管理
+ *
+ * @author Moriafly
+ * @since 2021年1月28日16:29:16
+ */
+interface LyricViewXInterface {
+
+ /**
+ * 设置整句之间的间隔高度
+ * @param height px
+ */
+ fun setSentenceDividerHeight(@Px height: Float)
+
+ /**
+ * 设置原句与翻译之间的间隔高度
+ * @param height px
+ */
+ fun setTranslateDividerHeight(@Px height: Float)
+
+ /**
+ * 设置歌词整体的垂直偏移值,配合[setHorizontalOffsetPercent]使用
+ * @param offset px
+ *
+ * @see [setHorizontalOffsetPercent]
+ */
+ fun setHorizontalOffset(@Px offset: Float)
+
+ /**
+ * 设置歌词整体的垂直偏移,相对于控件高度的百分比,0.5f即表示居中,配合[setHorizontalOffset]使用
+ *
+ * @param percent 0.0f ~ 1.0f
+ *
+ * @see [setHorizontalOffset]
+ */
+ fun setHorizontalOffsetPercent(@FloatRange(from = 0.0, to = 1.0) percent: Float)
+
+ /**
+ * 设置翻译相对与原词之间的缩放比例值
+ * @param scaleValue 一般来说 0.8f 是个不错的值
+ */
+ fun setTranslateTextScaleValue(@FloatRange(from = 0.1, to = 2.0) scaleValue: Float)
+
+ /**
+ * 设置文字的对齐方向
+ */
+ fun setTextGravity(gravity: Int)
+
+ /**
+ * 设置非当前行歌词字体颜色 [normalColor]
+ */
+ fun setNormalColor(@ColorInt normalColor: Int)
+
+ /**
+ * 普通歌词文本字体大小 [size],单位 px
+ */
+ fun setNormalTextSize(@Px size: Float)
+
+ /**
+ * 当前歌词文本字体大小
+ */
+ fun setCurrentTextSize(size: Float)
+
+ /**
+ * 设置当前行歌词的字体颜色
+ */
+ fun setCurrentColor(currentColor: Int)
+
+ /**
+ * 设置拖动歌词时选中歌词的字体颜色
+ */
+ fun setTimelineTextColor(timelineTextColor: Int)
+
+ /**
+ * 设置拖动歌词时时间线的颜色
+ */
+ fun setTimelineColor(timelineColor: Int)
+
+ /**
+ * 设置拖动歌词时右侧时间字体颜色
+ */
+ fun setTimeTextColor(timeTextColor: Int)
+
+ /**
+ * 设置歌词为空时屏幕中央显示的文字 [label],如“暂无歌词”
+ */
+ fun setLabel(label: String)
+
+ /**
+ * 加载歌词文本
+ * 两种语言的歌词时间戳需要一致
+ *
+ * @param mainLyricText 第一种语言歌词文本
+ * @param secondLyricText 可选,第二种语言歌词文本
+ */
+ fun loadLyric(mainLyricText: String?, secondLyricText: String? = null)
+
+ /**
+ * 加载歌词 [LyricEntry] 集合
+ * 如果你在 Service 等地方自行解析歌词包装成 [LyricEntry] 集合,那么可以使用此方法载入歌词
+ *
+ * @param lyricEntries 歌词集合
+ * @since 1.3.1
+ */
+ fun loadLyric(lyricEntries: List)
+
+ /**
+ * 刷新歌词
+ *
+ * @param time 当前播放时间
+ */
+ fun updateTime(time: Long, force: Boolean = false)
+
+ /**
+ * 设置歌词是否允许拖动
+ *
+ * @param draggable 是否允许拖动
+ * @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null
+ */
+ fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?)
+
+ /**
+ * 设置单击
+ */
+ fun setOnSingerClickListener(onSingerClickListener: OnSingleClickListener?)
+
+ /**
+ * 获取当前歌词每句实体,可用于歌词分享
+ *
+ * @return LyricEntry 集合
+ */
+ fun getLyricEntryList(): List
+
+ /**
+ * 设置当前歌词每句实体
+ */
+ fun setLyricEntryList(newList: List)
+
+ /**
+ * 获取当前行歌词
+ */
+ fun getCurrentLineLyricEntry(): LyricEntry?
+
+ /**
+ * 为歌词设置自定义的字体
+ *
+ * @param file 字体文件
+ */
+ fun setLyricTypeface(file: File)
+
+ /**
+ * 为歌词设置自定义的字体
+ *
+ * @param path 字体文件路径
+ */
+ fun setLyricTypeface(path: String)
+
+ /**
+ * 为歌词设置自定义的字体,可为空,若为空则应清除字体
+ *
+ * @param typeface 字体对象
+ */
+ fun setLyricTypeface(typeface: Typeface?)
+
+ /**
+ * 为歌词的过渡动画设置阻尼比(数值越大,回弹次数越多)
+ *
+ * @param dampingRatio 阻尼比 详见[androidx.dynamicanimation.animation.SpringForce]
+ */
+ fun setDampingRatioForLyric(dampingRatio: Float)
+
+ /**
+ * 为歌词视图的滚动动画设置阻尼比(数值越大,回弹次数越多)
+ *
+ * @param dampingRatio 阻尼比 详见[androidx.dynamicanimation.animation.SpringForce]
+ */
+ fun setDampingRatioForViewPort(dampingRatio: Float)
+
+ /**
+ * 为歌词的过渡动画设置刚度(数值越大,动画越短)
+ *
+ * @param stiffness 刚度 详见[androidx.dynamicanimation.animation.SpringForce]
+ */
+ fun setStiffnessForLyric(stiffness: Float)
+
+ /**
+ * 为歌词视图的滚动动画设置刚度(数值越大,动画越短)
+ *
+ * @param stiffness 刚度 详见[androidx.dynamicanimation.animation.SpringForce]
+ */
+ fun setStiffnessForViewPort(stiffness: Float)
+
+ /**
+ * 设置跳转播放按钮
+ */
+ fun setPlayDrawable(drawable: Drawable)
+
+ /**
+ * 设置是否绘制歌词翻译
+ */
+ fun setIsDrawTranslation(isDrawTranslation: Boolean)
+
+ /**
+ * 是否开启特定的模糊效果
+ */
+ fun setIsEnableBlurEffect(isEnableBlurEffect: Boolean)
+
+ /**
+ * 设置元素的偏移百分比,0.5f即表示居中
+ *
+ * @param itemOffsetPercent 0f ~ 1f 偏移百分比
+ */
+ fun setItemOffsetPercent(@FloatRange(from = 0.0, to = 1.0) itemOffsetPercent: Float)
+}
+
+/**
+ * 播放按钮点击监听器,点击后应该跳转到指定播放位置
+ */
+interface OnPlayClickListener {
+ /**
+ * 播放按钮被点击,应该跳转到指定播放位置
+ *
+ * @return 是否成功消费该事件,如果成功消费,则会更新UI
+ */
+ fun onPlayClick(time: Long): Boolean
+}
+
+/**
+ * 点击歌词布局
+ */
+interface OnSingleClickListener {
+ fun onClick()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt b/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt
new file mode 100644
index 0000000..17c590a
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/LyricEntry.kt
@@ -0,0 +1,82 @@
+package com.dirror.lyricviewx
+
+import android.os.Build
+import android.text.Layout
+import android.text.StaticLayout
+import android.text.TextPaint
+
+/**
+ * 一行歌词实体
+ * @since 2021年1月19日09:51:40 Moriafly 基于 LrcEntry 改造,转换为 kt ,移除部分过时方法
+ * @param time 歌词时间
+ * @param text 歌词文本
+ */
+class LyricEntry(val time: Long, val text: String) : Comparable {
+
+ /**
+ * 第二文本
+ */
+ var secondText: String? = null
+
+ /**
+ * staticLayout
+ */
+ var staticLayout: StaticLayout? = null
+ private set
+
+ var secondStaticLayout: StaticLayout? = null
+ private set
+
+ @Deprecated("存在不显示翻译的情况,会导致offset发生改变,故不再固定存储offset")
+ /**
+ * 歌词距离视图顶部的距离
+ */
+ var offset = Float.MIN_VALUE
+
+ /**
+ * 初始化
+ * @param textPaint 文本画笔
+ * @param width 宽度
+ * @param align 位置
+ */
+ fun init(
+ textPaint: TextPaint,
+ secondTextPaint: TextPaint,
+ width: Int, align: Layout.Alignment
+ ) {
+ staticLayout = createStaticLayout(text, textPaint, width, align)
+ secondStaticLayout = createStaticLayout(secondText, secondTextPaint, width, align)
+ offset = Float.MIN_VALUE
+ }
+
+ /**
+ * 继承 Comparable 比较
+ * @param other LyricEntry
+ * @return 时间差
+ */
+ override fun compareTo(other: LyricEntry): Int {
+ return (time - other.time).toInt()
+ }
+
+ companion object {
+ fun createStaticLayout(
+ text: String?,
+ paint: TextPaint,
+ width: Number,
+ align: Layout.Alignment
+ ): StaticLayout? {
+ if (text == null || text.isEmpty()) return null
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ StaticLayout.Builder
+ .obtain(text, 0, text.length, paint, width.toInt())
+ .setAlignment(align)
+ .setLineSpacing(0f, 1f)
+ .setIncludePad(false)
+ .build()
+ } else {
+ StaticLayout(text, paint, width.toInt(), align, 1f, 0f, false)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt b/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt
new file mode 100644
index 0000000..cb3b4b4
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/LyricUtil.kt
@@ -0,0 +1,289 @@
+package com.dirror.lyricviewx
+
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.graphics.Rect
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.view.MotionEvent
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
+import java.nio.charset.StandardCharsets
+import java.util.*
+import java.util.regex.Pattern
+
+/**
+ * 工具类
+ * 原 LrcUtils 转 Kotlin
+ */
+object LyricUtil {
+
+ private val PATTERN_LINE = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d{2,3}])+)(.+)")
+ private val PATTERN_TIME = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d{2,3})]")
+ private val argbEvaluator = ArgbEvaluator()
+
+ /**
+ * 从文件解析双语歌词
+ */
+ fun parseLrc(lrcFiles: Array?): List? {
+ if (lrcFiles == null || lrcFiles.size != 2 || lrcFiles[0] == null) {
+ return null
+ }
+ val mainLrcFile = lrcFiles[0]
+ val secondLrcFile = lrcFiles[1]
+ val mainEntryList = parseLrc(mainLrcFile)
+ val secondEntryList = parseLrc(secondLrcFile)
+ if (mainEntryList != null && secondEntryList != null) {
+ for (mainEntry in mainEntryList) {
+ for (secondEntry in secondEntryList) {
+ if (mainEntry.time == secondEntry.time) {
+ mainEntry.secondText = secondEntry.text
+ }
+ }
+ }
+ }
+ return mainEntryList
+ }
+
+ /**
+ * 从文件解析歌词
+ */
+ private fun parseLrc(lrcFile: File?): List? {
+ if (lrcFile == null || !lrcFile.exists()) {
+ return null
+ }
+ val entryList: MutableList = ArrayList()
+ try {
+ val br =
+ BufferedReader(InputStreamReader(FileInputStream(lrcFile), StandardCharsets.UTF_8))
+ var line: String
+ while (br.readLine().also { line = it } != null) {
+ val list = parseLine(line)
+ if (list != null && list.isNotEmpty()) {
+ entryList.addAll(list)
+ }
+ }
+ br.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ entryList.sort()
+ return entryList
+ }
+
+ /**
+ * 从文本解析双语歌词
+ */
+ fun parseLrc(lrcTexts: Array?): List? {
+ if (lrcTexts == null || lrcTexts.size != 2 || TextUtils.isEmpty(lrcTexts[0])) {
+ return null
+ }
+ val mainLrcText = lrcTexts[0]
+ val secondLrcText = lrcTexts[1]
+ val mainEntryList = mainLrcText?.let { parseLrc(it) }
+
+ /**
+ * 当输入的secondLrcText为空时,按如下格式解析歌词
+ * (音乐标签下载的第二种歌词格式)
+ *
+ * [00:21.11]いつも待ち合わせより15分前集合
+ * [00:21.11]总会比相约时间早15分钟集合
+ * [00:28.32]駅の改札ぬける
+ * [00:28.32]穿过车站的检票口
+ * [00:31.39]ざわめきにわくわくだね
+ * [00:31.39]嘈杂声令内心兴奋不已
+ * [00:35.23]どこへ向かうかなんて
+ * [00:35.23]不在意接下来要去哪里
+ */
+ if (TextUtils.isEmpty(secondLrcText)) {
+ var lastEntry: LyricEntry? = null
+ return mainEntryList?.filter { now ->
+ if (lastEntry == null) {
+ lastEntry = now
+ return@filter true
+ }
+
+ if (lastEntry!!.time == now.time) {
+ lastEntry!!.secondText = now.text
+ lastEntry = null
+ return@filter false
+ }
+
+ lastEntry = now
+ true
+ }
+ }
+
+ val secondEntryList = secondLrcText?.let { parseLrc(it) }
+ if (mainEntryList != null && secondEntryList != null) {
+ for (mainEntry in mainEntryList) {
+ for (secondEntry in secondEntryList) {
+ if (mainEntry.time == secondEntry.time) {
+ mainEntry.secondText = secondEntry.text
+ }
+ }
+ }
+ }
+ return mainEntryList
+ }
+
+ /**
+ * 从文本解析歌词
+ */
+ private fun parseLrc(lrcText: String): List? {
+ var lyricText = lrcText.trim()
+ if (TextUtils.isEmpty(lyricText)) return null
+
+ if (lyricText.startsWith("\uFEFF")) {
+ lyricText = lyricText.replace("\uFEFF", "")
+ }
+
+ // 针对传入 Language="Media Monkey Format"; Lyrics="......"; 的情况
+ lyricText = lyricText.substringAfter("Lyrics=\"")
+ .substringBeforeLast("\";")
+
+ val entryList: MutableList = ArrayList()
+ val array = lyricText.split("\\n".toRegex()).toTypedArray()
+ for (line in array) {
+ val list = parseLine(line)
+ if (!list.isNullOrEmpty()) {
+ entryList.addAll(list)
+ }
+ }
+ entryList.sort()
+ return entryList
+ }
+
+ /**
+ * 获取网络文本,需要在工作线程中执行
+ */
+ fun getContentFromNetwork(url: String?, charset: String?): String? {
+ var lrcText: String? = null
+ try {
+ val url = URL(url)
+ val conn = url.openConnection() as HttpURLConnection
+ conn.requestMethod = "GET"
+ conn.connectTimeout = 10000
+ conn.readTimeout = 10000
+ if (conn.responseCode == 200) {
+ val `is` = conn.inputStream
+ val bos = ByteArrayOutputStream()
+ val buffer = ByteArray(1024)
+ var len: Int
+ while (`is`.read(buffer).also { len = it } != -1) {
+ bos.write(buffer, 0, len)
+ }
+ `is`.close()
+ bos.close()
+ lrcText = bos.toString(charset)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return lrcText
+ }
+
+ /**
+ * 解析一行歌词
+ */
+ private fun parseLine(line: String): List? {
+ var lyricLine = line
+ if (TextUtils.isEmpty(lyricLine)) {
+ return null
+ }
+ lyricLine = lyricLine.trim { it <= ' ' }
+ // [00:17.65]让我掉下眼泪的
+ val lineMatcher = PATTERN_LINE.matcher(lyricLine)
+ if (!lineMatcher.matches()) {
+ return null
+ }
+ val times = lineMatcher.group(1)!!
+ val text = lineMatcher.group(3)!!
+ val entryList: MutableList = ArrayList()
+
+ // [00:17.65]
+ val timeMatcher = PATTERN_TIME.matcher(times)
+ while (timeMatcher.find()) {
+ val min = timeMatcher.group(1)!!.toLong()
+ val sec = timeMatcher.group(2)!!.toLong()
+ val milString = timeMatcher.group(3)!!
+ var mil = milString.toLong()
+ // 如果毫秒是两位数,需要乘以 10,when 新增支持 1 - 6 位毫秒,很多获取的歌词存在不同的毫秒位数
+ when (milString.length) {
+ 1 -> mil *= 100
+ 2 -> mil *= 10
+ 4 -> mil /= 10
+ 5 -> mil /= 100
+ 6 -> mil /= 1000
+ }
+ val time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil
+ entryList.add(LyricEntry(time, text))
+ }
+ return entryList
+ }
+
+ /**
+ * 转为[分:秒]
+ */
+ fun formatTime(milli: Long): String {
+ val m = (milli / DateUtils.MINUTE_IN_MILLIS).toInt()
+ val s = (milli / DateUtils.SECOND_IN_MILLIS % 60).toInt()
+ val mm = String.format(Locale.getDefault(), "%02d", m)
+ val ss = String.format(Locale.getDefault(), "%02d", s)
+ return "$mm:$ss"
+ }
+
+ /**
+ * BUG java.lang.NoSuchFieldException: No field sDurationScale in class Landroid/animation/ValueAnimator; #3
+ */
+ @SuppressLint("SoonBlockedPrivateApi")
+ @Deprecated("")
+ fun resetDurationScale() {
+ try {
+ val mField = ValueAnimator::class.java.getDeclaredField("sDurationScale")
+ mField.isAccessible = true
+ mField.setFloat(null, 1f)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ /**
+ * 结合fraction,计算两个值之间的比例
+ */
+ fun calcScaleValue(a: Float, b: Float, f: Float, reverse: Boolean = false): Float {
+ if (b == 0f) return 1f
+ return 1f + ((a - b) / b) * (if (reverse) 1f - f else f)
+ }
+
+ /**
+ * 颜色值插值函数
+ */
+ fun lerpColor(a: Int, b: Int, f: Float): Int {
+ return argbEvaluator.evaluate(f, a, b) as Int
+ }
+
+ /**
+ * 简单的插值函数
+ */
+ fun lerp(from: Float, to: Float, fraction: Float): Float {
+ return from + (to - from) * fraction
+ }
+
+ /**
+ * 判断MotionEvent是否发生在Rect中
+ */
+ fun MotionEvent.insideOf(rect: Rect?): Boolean {
+ rect ?: return false
+ return rect.contains(x.toInt(), y.toInt())
+ }
+
+ fun normalize(min: Float, max: Float, value: Float, limit: Boolean = false): Float {
+ if (min == max) return 1f
+ return ((value - min) / (max - min)).let {
+ if (limit) it.coerceIn(0f, 1f) else it
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt b/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt
new file mode 100644
index 0000000..71315e6
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/LyricViewX.kt
@@ -0,0 +1,1099 @@
+
+package com.dirror.lyricviewx
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import android.text.Layout
+import android.text.StaticLayout
+import android.text.TextPaint
+import android.text.format.DateUtils
+import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.MotionEvent
+import android.widget.Scroller
+import androidx.annotation.FloatRange
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.dynamicanimation.animation.springAnimationOf
+import androidx.dynamicanimation.animation.withSpringForceProperties
+import com.dirror.lyricviewx.LyricUtil.calcScaleValue
+import com.dirror.lyricviewx.LyricUtil.formatTime
+import com.dirror.lyricviewx.LyricUtil.insideOf
+import com.dirror.lyricviewx.LyricUtil.lerp
+import com.dirror.lyricviewx.LyricUtil.lerpColor
+import com.dirror.lyricviewx.LyricUtil.normalize
+import com.dirror.lyricviewx.extension.BlurMaskFilterExt
+import com.lalilu.easeview.EaseView
+import com.lalilu.easeview.animatevalue.BoolValue
+import com.lalilu.easeview.animatevalue.FloatListAnimateValue
+import com.muqingbfq.R
+import java.io.File
+import kotlin.concurrent.thread
+
+import kotlin.math.abs
+import kotlin.math.max
+
+/**
+ * LyricViewX
+ *
+ * Based on https://github.com/zion223/NeteaseCloudMusic-MVVM Kotlin
+ *
+ * Thanks:
+ * https://github.com/cy745
+ */
+open class LyricViewX : EaseView, LyricViewXInterface {
+ constructor(context: Context) : super(context) {
+ init(null)
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ init(attrs)
+ }
+
+ protected val readyHelper = ReadyHelper()
+ private val blurMaskFilterExt = BlurMaskFilterExt()
+
+ /** 单句歌词集合 */
+ private val lyricEntryList: MutableList = ArrayList()
+
+ /** 主歌词画笔 */
+ private val lyricPaint = TextPaint()
+
+ /** 副歌词(一般为翻译歌词)画笔 */
+ private val secondLyricPaint = TextPaint()
+
+ /** 时间文字画笔 */
+ private val timePaint = TextPaint()
+
+ private var timeFontMetrics: Paint.FontMetrics? = null
+
+ /** 跳转播放按钮 */
+ private var playDrawable: Drawable? = null
+
+ private var translateDividerHeight = 0f
+ private var sentenceDividerHeight = 0f
+ private var animationDuration: Long = 0
+ private var normalTextColor = 0
+ private var normalTextSize = 0f
+ private var currentTextColor = 0
+ private var currentTextSize = 0f
+ private var translateTextScaleValue = 1f
+ private var timelineTextColor = 0
+ private var timelineColor = 0
+ private var timeTextColor = 0
+ private var drawableWidth = 0
+ private var timeTextWidth = 0
+ private var defaultLabel: String? = null
+ private var lrcPadding = 0f
+ private var onPlayClickListener: OnPlayClickListener? = null
+ private var onSingerClickListener: OnSingleClickListener? = null
+ private var animator: ValueAnimator? = null
+ private var gestureDetector: GestureDetector? = null
+ private var scroller: Scroller? = null
+ private var flag: Any? = null
+ private var isTouching = false
+ private var isFling = false
+ private var textGravity = GRAVITY_CENTER // 歌词显示位置,靠左 / 居中 / 靠右
+ private var horizontalOffset: Float = 0f
+ private var horizontalOffsetPercent: Float = 0.5f
+ private var itemOffsetPercent: Float = 0.5f
+ private var dampingRatioForLyric: Float = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ private var dampingRatioForViewPort: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ private var stiffnessForLyric: Float = SpringForce.STIFFNESS_LOW
+ private var stiffnessForViewPort: Float = SpringForce.STIFFNESS_VERY_LOW
+
+ private var currentLine = 0 // 当前高亮显示的歌词
+ private val focusLine: Int // 当前焦点歌词
+ get() = if (isTouching || isFling) centerLine else currentLine
+
+ /**
+ * 获取当前在视图中央的行数
+ */
+ private val centerLine: Int
+ get() {
+ var centerLine = 0
+ var minDistance = Float.MAX_VALUE
+ var tempDistance: Float
+
+ for (i in lyricEntryList.indices) {
+ tempDistance = abs(mViewPortOffset - getOffset(i))
+ if (tempDistance < minDistance) {
+ minDistance = tempDistance
+ centerLine = i
+ }
+ }
+ return centerLine
+ }
+
+ /**
+ * 获取歌词宽度
+ */
+ open val lrcWidth: Float
+ get() = width - lrcPadding * 2
+
+ /**
+ * 歌词整体的垂直偏移值
+ */
+ open val startOffset: Float
+ get() = height.toFloat() * horizontalOffsetPercent + horizontalOffset
+
+
+ /**
+ * 原有的mOffset被拆分成两个独立的offset,这样可以更好地让进度和拖拽滚动独立开来
+ */
+ private var mCurrentOffset = 0f // 实际的歌词进度Offset
+ private var mViewPortOffset = 0f // 歌词显示窗口的Offset
+
+ private var animateProgress = 0f // 动画进度
+ private var animateTargetOffset = 0f // 动画目标Offset
+ private var animateStartOffset = 0f // 动画起始Offset
+
+ private val viewPortSpringAnimator = springAnimationOf(
+ getter = { mViewPortOffset },
+ setter = { value ->
+ if (!isShowTimeline.value && !isTouching && !isFling) {
+ mViewPortOffset = value
+ invalidate()
+ }
+ }
+ ).withSpringForceProperties {
+ dampingRatio = dampingRatioForViewPort
+ stiffness = stiffnessForViewPort
+ finalPosition = 0f
+ }
+
+ /**
+ * 弹性动画Scroller
+ */
+ private val progressSpringAnimator = springAnimationOf(
+ getter = { mCurrentOffset },
+ setter = { value ->
+ animateProgress = normalize(animateStartOffset, animateTargetOffset, value)
+ mCurrentOffset = value
+
+ if (!isShowTimeline.value && !isTouching && !isFling) {
+ viewPortSpringAnimator.animateToFinalPosition(animateTargetOffset)
+ }
+ invalidate()
+ }
+ ).withSpringForceProperties {
+ dampingRatio = dampingRatioForLyric
+ stiffness = stiffnessForLyric
+ finalPosition = 0f
+ }
+
+ @SuppressLint("CustomViewStyleable")
+ private fun init(attrs: AttributeSet?) {
+ readyHelper.readyState = STATE_INITIALIZING
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LyricView)
+ currentTextSize = typedArray.getDimension(R.styleable.LyricView_lrcTextSize, resources.getDimension(R.dimen.lrc_text_size))
+ normalTextSize = typedArray.getDimension(R.styleable.LyricView_lrcNormalTextSize, resources.getDimension(R.dimen.lrc_text_size))
+ if (normalTextSize == 0f) {
+ normalTextSize = currentTextSize
+ }
+
+ sentenceDividerHeight =
+ typedArray.getDimension(R.styleable.LyricView_lrcSentenceDividerHeight, resources.getDimension(R.dimen.lrc_sentence_divider_height))
+ translateDividerHeight =
+ typedArray.getDimension(R.styleable.LyricView_lrcTranslateDividerHeight, resources.getDimension(R.dimen.lrc_translate_divider_height))
+ val defDuration = resources.getInteger(R.integer.lrc_animation_duration)
+ animationDuration = typedArray.getInt(R.styleable.LyricView_lrcAnimationDuration, defDuration).toLong()
+ animationDuration =
+ if (animationDuration < 0) defDuration.toLong() else animationDuration
+
+ normalTextColor = typedArray.getColor(
+ R.styleable.LyricView_lrcNormalTextColor,
+ ContextCompat.getColor(context, R.color.lrc_normal_text_color)
+ )
+ currentTextColor = typedArray.getColor(
+ R.styleable.LyricView_lrcCurrentTextColor,
+ ContextCompat.getColor(context, R.color.lrc_current_text_color)
+ )
+ timelineTextColor = typedArray.getColor(
+ R.styleable.LyricView_lrcTimelineTextColor,
+ ContextCompat.getColor(context, R.color.lrc_timeline_text_color)
+ )
+ defaultLabel = typedArray.getString(R.styleable.LyricView_lrcLabel)
+ defaultLabel = if (defaultLabel.isNullOrEmpty()) "暂无歌词" else defaultLabel
+ lrcPadding = typedArray.getDimension(R.styleable.LyricView_lrcPadding, 0f)
+ timelineColor = typedArray.getColor(
+ R.styleable.LyricView_lrcTimelineColor,
+ ContextCompat.getColor(context, R.color.lrc_timeline_color)
+ )
+ val timelineHeight = typedArray.getDimension(
+ R.styleable.LyricView_lrcTimelineHeight,
+ resources.getDimension(R.dimen.lrc_timeline_height)
+ )
+ playDrawable = typedArray.getDrawable(R.styleable.LyricView_lrcPlayDrawable)
+ playDrawable = if (playDrawable == null) ContextCompat.getDrawable(
+ context,
+ R.drawable.zt
+ ) else playDrawable
+ timeTextColor = typedArray.getColor(
+ R.styleable.LyricView_lrcTimeTextColor,
+ ContextCompat.getColor(context, R.color.lrc_time_text_color)
+ )
+ val timeTextSize = typedArray.getDimension(
+ R.styleable.LyricView_lrcTimeTextSize,
+ resources.getDimension(R.dimen.lrc_time_text_size)
+ )
+ textGravity = typedArray.getInteger(R.styleable.LyricView_lrcTextGravity, GRAVITY_CENTER)
+ translateTextScaleValue = typedArray.getFloat(R.styleable.LyricView_lrcTranslateTextScaleValue, 1f)
+ horizontalOffset = typedArray.getDimension(R.styleable.LyricView_lrcHorizontalOffset, 0f)
+ horizontalOffsetPercent = typedArray.getDimension(R.styleable.LyricView_lrcHorizontalOffsetPercent, 0.5f)
+ itemOffsetPercent = typedArray.getDimension(R.styleable.LyricView_lrcItemOffsetPercent, 0.5f)
+ isDrawTranslation = typedArray.getBoolean(R.styleable.LyricView_lrcIsDrawTranslation, false)
+ typedArray.recycle()
+ drawableWidth = resources.getDimension(R.dimen.lrc_drawable_width).toInt()
+ timeTextWidth = resources.getDimension(R.dimen.lrc_time_width).toInt()
+ lyricPaint.isAntiAlias = true
+ lyricPaint.textSize = currentTextSize
+ lyricPaint.textAlign = Paint.Align.LEFT
+// lyricPaint.setShadowLayer(0.1f, 0f, 1f, Color.DKGRAY)
+ secondLyricPaint.isAntiAlias = true
+ secondLyricPaint.textSize = currentTextSize
+ secondLyricPaint.textAlign = Paint.Align.LEFT
+// secondLyricPaint.setShadowLayer(0.1f, 0f, 1f, Color.DKGRAY)
+ timePaint.isAntiAlias = true
+ timePaint.textSize = timeTextSize
+ timePaint.textAlign = Paint.Align.CENTER
+ timePaint.strokeWidth = timelineHeight
+ timePaint.strokeCap = Paint.Cap.ROUND
+ timeFontMetrics = timePaint.fontMetrics
+ gestureDetector = GestureDetector(context, mSimpleOnGestureListener)
+ gestureDetector!!.setIsLongpressEnabled(false)
+ scroller = Scroller(context)
+ }
+
+ /**
+ * 歌词是否有效
+ * @return true,如果歌词有效,否则false
+ */
+ private fun hasLrc(): Boolean {
+ return lyricEntryList.isNotEmpty()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ if (changed) {
+ initPlayDrawable()
+ initEntryList()
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ }
+ readyHelper.readyState = STATE_INITIALIZED
+ }
+
+ private val isShowTimeline = BoolValue().also(::registerValue)
+ private val isEnableBlurEffect = BoolValue().also(::registerValue)
+ private val progressKeeper = FloatListAnimateValue().also(::registerValue)
+ private val blurProgressKeeper = FloatListAnimateValue().also(::registerValue)
+
+ private val heightKeeper = LinkedHashMap()
+ private val offsetKeeper = LinkedHashMap()
+ private val minOffsetKeeper = LinkedHashMap()
+ private val maxOffsetKeeper = LinkedHashMap()
+
+ private var viewPortStartOffset: Float = 0f
+ private var isDrawTranslationValue = 0f
+ private var isDrawTranslation: Boolean = false
+ set(value) {
+ if (field == value) return
+ field = value
+ viewPortStartOffset = mViewPortOffset
+ isDrawTranslationAnimator.animateToFinalPosition(if (value) 1000f else 0f)
+ }
+ private val isDrawTranslationAnimator = springAnimationOf(
+ getter = { isDrawTranslationValue * 1000f },
+ setter = {
+ isDrawTranslationValue = it / 1000f
+
+ if (!isTouching && !isFling) {
+ viewPortSpringAnimator.cancel()
+
+ val targetOffset = if (isDrawTranslation) getMaxOffset(focusLine) else getMinOffset(focusLine)
+ val animateValue = if (isDrawTranslation) isDrawTranslationValue else 1f - isDrawTranslationValue
+
+ mViewPortOffset = lerp(viewPortStartOffset, targetOffset, animateValue)
+ }
+ invalidate()
+ },
+ ).withSpringForceProperties {
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ stiffness = SpringForce.STIFFNESS_LOW
+ finalPosition = if (isDrawTranslation) 1000f else 0f
+ }
+
+ override fun onPreDraw(canvas: Canvas): Boolean {
+ // 无歌词,只渲染一句无歌词的提示语句
+ if (!hasLrc()) {
+ lyricPaint.color = currentTextColor
+ lyricPaint.textSize = normalTextSize
+ LyricEntry.createStaticLayout(
+ defaultLabel,
+ lyricPaint,
+ lrcWidth,
+ Layout.Alignment.ALIGN_CENTER
+ )?.let {
+ drawText(
+ canvas = canvas,
+ staticLayout = it,
+ calcHeightOnly = false,
+ yOffset = startOffset,
+ yClipPercentage = 1f
+ )
+ }
+ return false
+ }
+ return super.onPreDraw(canvas)
+ }
+
+ override fun onDoDraw(canvas: Canvas): Boolean {
+ val centerY = startOffset
+ val currentCenterLine = centerLine
+
+ // 当显示时间线时,需要绘制时间线
+ if (isShowTimeline.value || isShowTimeline.animateValue > 0f) {
+ val alpha = (isShowTimeline.animateValue * 255f).toInt()
+
+ // 绘制播放按钮
+ playDrawable?.let {
+ it.alpha = alpha
+ it.draw(canvas)
+ }
+
+ // 绘制时间线
+ timePaint.color = timelineColor
+ timePaint.alpha = alpha
+ canvas.drawLine(
+ timeTextWidth.toFloat(), centerY,
+ (width - timeTextWidth).toFloat(), centerY, timePaint
+ )
+
+ // 绘制当前时间
+ val timeText = formatTime(lyricEntryList[currentCenterLine].time)
+ val timeX = width - timeTextWidth.toFloat() / 2
+ val timeY = centerY - (timeFontMetrics!!.descent + timeFontMetrics!!.ascent) / 2
+ timePaint.color = timeTextColor
+ timePaint.alpha = alpha
+ canvas.drawText(timeText, timeX, timeY, timePaint)
+ }
+
+ canvas.translate(0f, mViewPortOffset)
+
+ var yOffset = 0f
+ var yMinOffset = 0f
+ var yMaxOffset = 0f
+ var scaleValue: Float
+ var progress: Float
+ var radius: Int
+ var calcHeightOnly: Boolean
+
+ for (i in lyricEntryList.indices) {
+ // 根据上一项所计算得到的offset值,判断当前元素是否在需要绘制的区间,如果不在,则只需要计算高度不进行绘制相关计算
+ calcHeightOnly = getOffset(i - 1) !in (mViewPortOffset - height)..(mViewPortOffset + height)
+ progressKeeper.updateTargetValue(i, if (currentLine == i) animateProgress else 0f)
+ progress = progressKeeper.getValueByIndex(i)
+ scaleValue = 1f
+ radius = 0
+
+ if (!calcHeightOnly) {
+ when {
+ // 当前行动画未结束
+ progress > 0f -> {
+ scaleValue = calcScaleValue(currentTextSize, normalTextSize, progress)
+ lyricPaint.color = lerpColor(normalTextColor, currentTextColor, progress.coerceIn(0f, 1f))
+ }
+
+ isShowTimeline.value && i == currentCenterLine -> {
+ lyricPaint.color = timelineTextColor
+ }
+
+ else -> {
+ lyricPaint.color = normalTextColor
+ }
+ }
+ lyricPaint.textSize = normalTextSize
+ secondLyricPaint.textSize = lyricPaint.textSize * translateTextScaleValue
+ secondLyricPaint.color = lyricPaint.color
+
+ if (isEnableBlurEffect.value || isEnableBlurEffect.animateValue > 0f) {
+ radius = when (i) {
+ currentCenterLine -> 0
+ currentCenterLine + 1 -> 3
+ currentCenterLine + 2, currentCenterLine - 1 -> 7
+ currentCenterLine + 3, currentCenterLine - 2 -> 11
+ currentCenterLine + 4, currentCenterLine - 3 -> 20
+ else -> 20
+ }
+ blurProgressKeeper.updateTargetValue(i, radius.toFloat())
+ radius = blurProgressKeeper.getValueByIndex(i).toInt()
+ radius = (radius * isEnableBlurEffect.animateValue).toInt()
+ }
+ }
+
+ val itemHeight = drawLyricEntry(
+ canvas = canvas,
+ entry = lyricEntryList[i],
+ calcHeightOnly = calcHeightOnly,
+ yOffset = yOffset,
+ scaleValue = scaleValue,
+ blurRadius = radius,
+ ) { minHeight, maxHeight ->
+ minOffsetKeeper[i] = yMinOffset + calcOffsetOfItem(minHeight, sentenceDividerHeight)
+ yMinOffset += minHeight
+
+ maxOffsetKeeper[i] = yMaxOffset + calcOffsetOfItem(maxHeight, sentenceDividerHeight)
+ yMaxOffset += maxHeight
+ }
+ heightKeeper[i] = itemHeight
+ offsetKeeper[i] = yOffset + calcOffsetOfItem(itemHeight, sentenceDividerHeight)
+ yOffset += itemHeight
+ }
+ return super.onDoDraw(canvas)
+ }
+
+ /**
+ * 画一组歌词语句
+ *
+ * @param calcHeightOnly 是否只计算高度
+ * @param yOffset 歌词中心 Y 坐标
+ * @param scaleValue 缩放比例
+ * @param blurRadius 模糊半径
+ *
+ * @return 该组歌词的实际绘制高度
+ */
+ private fun drawLyricEntry(
+ canvas: Canvas,
+ entry: LyricEntry,
+ calcHeightOnly: Boolean,
+ yOffset: Float,
+ scaleValue: Float,
+ blurRadius: Int,
+ callback: (minHeight: Float, maxHeight: Float) -> Unit = { _, _ -> }
+ ): Float {
+ var tempHeight = 0f
+ var minTempHeight = 0f
+ var maxTempHeight = 0f
+
+ entry.staticLayout?.let {
+ tempHeight += drawText(
+ canvas = canvas,
+ staticLayout = it,
+ calcHeightOnly = calcHeightOnly,
+ yOffset = yOffset,
+ yClipPercentage = 1f,
+ scale = scaleValue,
+ blurRadius = blurRadius
+ )
+ minTempHeight = tempHeight
+ maxTempHeight = tempHeight
+
+ entry.secondStaticLayout?.let { second ->
+ tempHeight += translateDividerHeight * isDrawTranslationValue
+ maxTempHeight += translateDividerHeight
+
+ tempHeight += drawText(
+ canvas = canvas,
+ staticLayout = second,
+ calcHeightOnly = calcHeightOnly,
+ yOffset = yOffset + tempHeight,
+ yClipPercentage = isDrawTranslationValue,
+ alpha = isDrawTranslationValue,
+ scale = scaleValue,
+ blurRadius = blurRadius
+ ) { _, max ->
+ maxTempHeight += max
+ }
+ }
+ tempHeight += sentenceDividerHeight
+ minTempHeight += sentenceDividerHeight
+ maxTempHeight += sentenceDividerHeight
+ }
+ callback(minTempHeight, maxTempHeight)
+ return tempHeight
+ }
+
+ /**
+ * 画一行歌词
+ *
+ * @param calcHeightOnly 是否只计算高度
+ * @param yOffset 歌词中心 Y 坐标
+ * @param yClipPercentage 垂直裁剪比例
+ * @param scale 缩放比例
+ * @param alpha 透明度
+ * @param blurRadius 模糊半径 实现类似AppleMusic的歌词语句的模糊效果
+ *
+ * @return 实际绘制高度
+ */
+ private fun drawText(
+ canvas: Canvas,
+ staticLayout: StaticLayout,
+ calcHeightOnly: Boolean = false,
+ yOffset: Float,
+ @FloatRange(from = 0.0, to = 1.0)
+ yClipPercentage: Float = 1f,
+ scale: Float = 1f,
+ alpha: Float = 1f,
+ blurRadius: Int = 0,
+ callback: (minHeight: Float, maxHeight: Float) -> Unit = { _, _ -> }
+ ): Float {
+ if (staticLayout.lineCount == 0) {
+ callback(0f, 0f)
+ return 0f
+ }
+ if (calcHeightOnly) {
+ callback(0f, staticLayout.height.toFloat())
+ return staticLayout.height * yClipPercentage
+ }
+ val lineHeight = staticLayout.height.toFloat() / staticLayout.lineCount.toFloat()
+
+ var yTemp = 0f // y轴临时偏移量
+ var pivotYTemp: Float // 缩放中心Y坐标
+ var itemActualHeight: Float // 单行实际绘制高度
+ var actualHeight = 0f // 实际绘制高度
+
+ staticLayout.paint.alpha = (alpha * 255f).toInt()
+ staticLayout.paint.maskFilter = blurMaskFilterExt.get(blurRadius)
+
+ /**
+ * 由于对StaticLayout整个缩放会使其中间的行间距也被缩放(通过TextPaint的textSize缩放则不会),
+ * 导致其真实渲染高度大于StaticLayout的height属性的值,同时也没有其他的接口能实现相同的缩放效果(对TextSize缩放会显得卡卡的)
+ *
+ * 所以通过Canvas的clipRect,来分别对StaticLayout的每一行文字进行缩放和绘制(StaticLayout的各行高度是一致的)
+ */
+ repeat(staticLayout.lineCount) {
+ itemActualHeight = lineHeight * yClipPercentage
+ pivotYTemp = yTemp + itemActualHeight - staticLayout.paint.descent() // TextPaint修改textSize所实现的缩放效果应该就是descent线上的缩放(感觉效果差不多)
+
+ canvas.save()
+ canvas.translate(lrcPadding, yOffset)
+ canvas.clipRect(-lrcPadding, yTemp, staticLayout.width.toFloat() + lrcPadding, yTemp + itemActualHeight)
+
+ // 根据文字的gravity设置缩放基点坐标
+ when (textGravity) {
+ GRAVITY_LEFT -> canvas.scale(scale, scale, 0f, pivotYTemp)
+ GRAVITY_RIGHT -> {
+ canvas.scale(scale, scale, staticLayout.width.toFloat(), pivotYTemp)
+ }
+
+ GRAVITY_CENTER -> {
+ canvas.scale(scale, scale, staticLayout.width / 2f, pivotYTemp)
+ }
+ }
+ staticLayout.draw(canvas)
+ canvas.restore()
+ yTemp += itemActualHeight
+ actualHeight += itemActualHeight
+ }
+ callback(0f, staticLayout.height.toFloat())
+ return actualHeight
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
+ isTouching = false
+ if (hasLrc() && !isFling) {
+ // TODO 应该为Timeline独立设置一个Enable开关, 这样就可以不需要等待Timeline消失
+ postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
+ }
+ }
+ return gestureDetector!!.onTouchEvent(event)
+ }
+
+ /**
+ * 手势监听器
+ */
+ private val mSimpleOnGestureListener: SimpleOnGestureListener =
+ object : SimpleOnGestureListener() {
+
+ override fun onDown(e: MotionEvent): Boolean {
+ // 有歌词并且设置了 mOnPlayClickListener
+ if (hasLrc() && onPlayClickListener != null) {
+ scroller!!.forceFinished(true)
+ removeCallbacks(hideTimelineRunnable)
+ isTouching = true
+ invalidate()
+ return true
+ }
+ return super.onDown(e)
+ }
+
+ override fun onScroll(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (hasLrc()) {
+ // 如果没显示 Timeline 的时候,distanceY 一段距离后再显示时间线
+ if (!isShowTimeline.value && abs(distanceY) >= 10) {
+ // 滚动显示时间线
+ isShowTimeline.value = true
+ }
+ mViewPortOffset += -distanceY
+ mViewPortOffset.coerceIn(getOffset(lyricEntryList.size - 1), getOffset(0))
+ invalidate()
+ return true
+ }
+ return super.onScroll(e1, e2, distanceX, distanceY)
+ }
+
+ override fun onFling(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ if (hasLrc()) {
+ scroller!!.fling(
+ 0, mViewPortOffset.toInt(), 0,
+ velocityY.toInt(), 0, 0,
+ getOffset(lyricEntryList.size - 1).toInt(),
+ getOffset(0).toInt()
+ )
+ isFling = true
+ return true
+ }
+ return super.onFling(e1, e2, velocityX, velocityY)
+ }
+
+ override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
+ if (!hasLrc() || !isShowTimeline.value || !e.insideOf(playDrawable?.bounds)) {
+ onSingerClickListener?.onClick()
+ return super.onSingleTapConfirmed(e)
+ }
+
+ val centerLine = centerLine
+ val centerLineTime = lyricEntryList[centerLine].time
+ // onPlayClick 消费了才更新 UI
+ if (onPlayClickListener?.onPlayClick(centerLineTime) == true) {
+ isShowTimeline.value = false
+ removeCallbacks(hideTimelineRunnable)
+ smoothScrollTo(centerLine)
+ invalidate()
+ return true
+ }
+ return super.onSingleTapConfirmed(e)
+ }
+ }
+
+ private val hideTimelineRunnable = Runnable {
+ if (hasLrc() && isShowTimeline.value) {
+ isShowTimeline.value = false
+ smoothScrollTo(currentLine)
+ }
+ }
+
+ override fun computeScroll() {
+ if (scroller!!.computeScrollOffset()) {
+ mViewPortOffset = scroller!!.currY.toFloat()
+ invalidate()
+ }
+ if (isFling && scroller!!.isFinished) {
+ isFling = false
+ if (hasLrc() && !isTouching) {
+ adjustCenter()
+ postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
+ }
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ removeCallbacks(hideTimelineRunnable)
+ super.onDetachedFromWindow()
+ }
+
+ private fun onLrcLoaded(entryList: List?) {
+ if (!entryList.isNullOrEmpty()) {
+ lyricEntryList.addAll(entryList)
+ }
+ lyricEntryList.sort()
+ initEntryList()
+ invalidate()
+ }
+
+ private fun initPlayDrawable() {
+ val l = (timeTextWidth - drawableWidth) / 2
+ val t = startOffset.toInt() - drawableWidth / 2
+ val r = l + drawableWidth
+ val b = t + drawableWidth
+ playDrawable!!.setBounds(l, t, r, b)
+ }
+
+ private fun initEntryList() {
+ if (!hasLrc() || width == 0) {
+ return
+ }
+ /**
+ * StaticLayout 根据初始化时传入的 TextSize 计算换行的位置
+ * 如果 [currentTextSize] 与 [normalTextSize] 相差较大,
+ * 则会导致歌词渲染时溢出边界,或行间距不足挤压在一起
+ *
+ * 故计算出可能的最大 TextSize 以后,用其初始化,使 StaticLayout 拥有足够的高度
+ */
+ lyricPaint.textSize = max(currentTextSize, normalTextSize)
+ secondLyricPaint.textSize = lyricPaint.textSize * translateTextScaleValue
+ for (lrcEntry in lyricEntryList) {
+ lrcEntry.init(
+ lyricPaint, secondLyricPaint,
+ lrcWidth.toInt(), textGravity.toLayoutAlign()
+ )
+ }
+ mCurrentOffset = startOffset
+ mViewPortOffset = startOffset
+ }
+
+ private fun reset() {
+ // TODO 待完善reset的逻辑
+ scroller!!.forceFinished(true)
+ isShowTimeline.value = false
+ isTouching = false
+ isFling = false
+ removeCallbacks(hideTimelineRunnable)
+ lyricEntryList.clear()
+ mCurrentOffset = 0f
+ mViewPortOffset = 0f
+ currentLine = 0
+ invalidate()
+ }
+
+ /**
+ * 将中心行微调至正中心
+ */
+ private fun adjustCenter() {
+ smoothScrollTo(currentLine)
+ }
+
+ /**
+ * 平滑滚动过渡到某一行
+ *
+ * @param line 行号
+ */
+ private fun smoothScrollTo(line: Int) {
+ val offset = getOffset(line)
+ animateStartOffset = mCurrentOffset
+ animateTargetOffset = offset
+ progressSpringAnimator.animateToFinalPosition(offset)
+ }
+
+ /**
+ * 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数)
+ */
+ private fun findShowLine(time: Long): Int {
+ var left = 0
+ var right = lyricEntryList.size
+ while (left <= right) {
+ val middle = (left + right) / 2
+ val middleTime = lyricEntryList[middle].time
+ if (time < middleTime) {
+ right = middle - 1
+ } else {
+ if (middle + 1 >= lyricEntryList.size || time < lyricEntryList[middle + 1].time) {
+ return middle
+ }
+ left = middle + 1
+ }
+ }
+ return 0
+ }
+
+ /**
+ * 计算单个歌词元素的偏移量,用于控制歌词对其中线的位置
+ *
+ * 计算出来的歌词高度包含了分割线的高度,所以需要减去分割线的高度
+ *
+ * @param itemHeight 歌词元素的高度
+ * @param dividerHeight 分割线的高度
+ *
+ * @return 歌词元素的偏移量
+ */
+ protected open fun calcOffsetOfItem(itemHeight: Float, dividerHeight: Float): Float {
+ return (itemHeight - dividerHeight) * itemOffsetPercent
+ }
+
+ /**
+ * 因为添加了 [translateDividerHeight] 用来间隔开歌词与翻译,
+ * 所以直接从 [LyricEntry] 获取高度不可行,
+ * 故使用该 [getLyricHeight] 方法来计算 [LyricEntry] 的高度
+ */
+ @Deprecated("不再单独计算歌词的高度,在绘制时计算并进行更新缓存,所见即所得")
+ open fun getLyricHeight(line: Int): Int {
+ var height = lyricEntryList[line].staticLayout?.height ?: return 0
+ lyricEntryList[line].secondStaticLayout?.height?.let {
+ height += (it + translateDividerHeight).toInt()
+ }
+ return height
+ }
+
+ /**
+ * 获取歌词距离视图顶部的距离
+ */
+ private fun getOffset(line: Int): Float {
+ return startOffset - (offsetKeeper[line] ?: 0f)
+ }
+
+ private fun getMinOffset(line: Int): Float {
+ return startOffset - (minOffsetKeeper[line] ?: 0f)
+ }
+
+ private fun getMaxOffset(line: Int): Float {
+ return startOffset - (maxOffsetKeeper[line] ?: 0f)
+ }
+
+ /**
+ * 在主线程中运行
+ */
+ private fun runOnMain(r: Runnable) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ r.run()
+ } else {
+ post(r)
+ }
+ }
+
+ /**
+ * 以下是公共部分
+ * 用法见接口 [LyricViewXInterface]
+ */
+
+ override fun setSentenceDividerHeight(height: Float) {
+ sentenceDividerHeight = height
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ postInvalidate()
+ }
+
+ override fun setTranslateDividerHeight(height: Float) {
+ translateDividerHeight = height
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ postInvalidate()
+ }
+
+ override fun setHorizontalOffset(offset: Float) {
+ horizontalOffset = offset
+ initPlayDrawable()
+ postInvalidate()
+ }
+
+ override fun setHorizontalOffsetPercent(percent: Float) {
+ horizontalOffsetPercent = percent
+ initPlayDrawable()
+ postInvalidate()
+ }
+
+ override fun setTranslateTextScaleValue(scaleValue: Float) {
+ translateTextScaleValue = scaleValue
+ initEntryList()
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ }
+
+ override fun setTextGravity(gravity: Int) {
+ textGravity = gravity
+ initEntryList()
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ }
+
+ override fun setNormalColor(normalColor: Int) {
+ normalTextColor = normalColor
+ postInvalidate()
+ }
+
+ override fun setNormalTextSize(size: Float) {
+ normalTextSize = size
+ initEntryList()
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ }
+
+ override fun setCurrentTextSize(size: Float) {
+ currentTextSize = size
+ initEntryList()
+ if (hasLrc()) {
+ smoothScrollTo(currentLine)
+ }
+ }
+
+ override fun setCurrentColor(currentColor: Int) {
+ currentTextColor = currentColor
+ postInvalidate()
+ }
+
+ override fun setTimelineTextColor(timelineTextColor: Int) {
+ this.timelineTextColor = timelineTextColor
+ postInvalidate()
+ }
+
+ override fun setTimelineColor(timelineColor: Int) {
+ this.timelineColor = timelineColor
+ postInvalidate()
+ }
+
+ override fun setTimeTextColor(timeTextColor: Int) {
+ this.timeTextColor = timeTextColor
+ postInvalidate()
+ }
+
+ override fun setLabel(label: String) {
+ runOnMain {
+ defaultLabel = label
+ this@LyricViewX.invalidate()
+ }
+ }
+
+ override fun loadLyric(mainLyricText: String?, secondLyricText: String?) {
+ runOnMain {
+ reset()
+ val sb = StringBuilder("file://")
+ sb.append(mainLyricText)
+ if (secondLyricText != null) {
+ sb.append("#").append(secondLyricText)
+ }
+ val flag = sb.toString()
+ this@LyricViewX.flag = flag
+ thread {
+ val lrcEntries = LyricUtil.parseLrc(arrayOf(mainLyricText, secondLyricText))
+ runOnMain {
+ if (flag === flag) {
+ onLrcLoaded(lrcEntries)
+ this@LyricViewX.flag = null
+ }
+ }
+ }
+ }
+ }
+
+ override fun loadLyric(lyricEntries: List) {
+ runOnMain {
+ reset()
+ onLrcLoaded(lyricEntries)
+ }
+ }
+
+ override fun updateTime(time: Long, force: Boolean) {
+ // 将方法的执行延后至 View 创建完成后执行
+ readyHelper.whenReady {
+ if (!it) return@whenReady
+ if (hasLrc()) {
+ val line = findShowLine(time)
+ if (line != currentLine) {
+ runOnMain {
+ currentLine = line
+ smoothScrollTo(line)
+ }
+ }
+ }
+ }
+ }
+
+ override fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
+ this.onPlayClickListener = if (draggable) {
+ requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
+ onPlayClickListener
+ } else {
+ null
+ }
+ }
+
+ override fun setOnSingerClickListener(onSingerClickListener: OnSingleClickListener?) {
+ this.onSingerClickListener = onSingerClickListener
+ }
+
+ override fun getLyricEntryList(): List {
+ return lyricEntryList.toList()
+ }
+
+ override fun setLyricEntryList(newList: List) {
+ reset()
+ onLrcLoaded(newList)
+ this@LyricViewX.flag = null
+ }
+
+ override fun getCurrentLineLyricEntry(): LyricEntry? {
+ if (currentLine <= lyricEntryList.lastIndex) {
+ return lyricEntryList[currentLine]
+ }
+ return null
+ }
+
+ override fun setLyricTypeface(file: File) {
+ val typeface = file.takeIf { it.exists() }
+ ?.runCatching { Typeface.createFromFile(this) }
+ ?.getOrNull() ?: return
+
+ setLyricTypeface(typeface)
+ }
+
+ override fun setLyricTypeface(path: String) {
+ setLyricTypeface(File(path))
+ }
+
+ override fun setLyricTypeface(typeface: Typeface?) {
+ lyricPaint.typeface = typeface
+ secondLyricPaint.typeface = typeface
+ postInvalidate()
+ }
+
+ override fun setDampingRatioForLyric(dampingRatio: Float) {
+ dampingRatioForLyric = dampingRatio
+ progressSpringAnimator.spring.dampingRatio = dampingRatio
+ }
+
+ override fun setDampingRatioForViewPort(dampingRatio: Float) {
+ dampingRatioForViewPort = dampingRatio
+ viewPortSpringAnimator.spring.dampingRatio = dampingRatio
+ }
+
+ override fun setStiffnessForLyric(stiffness: Float) {
+ stiffnessForLyric = stiffness
+ progressSpringAnimator.spring.stiffness = stiffness
+ }
+
+ override fun setStiffnessForViewPort(stiffness: Float) {
+ stiffnessForViewPort = stiffness
+ viewPortSpringAnimator.spring.stiffness = stiffness
+ }
+
+ override fun setPlayDrawable(drawable: Drawable) {
+ playDrawable = drawable
+ }
+
+ override fun setIsDrawTranslation(isDrawTranslation: Boolean) {
+ this.isDrawTranslation = isDrawTranslation
+ postInvalidate()
+ }
+
+ override fun setIsEnableBlurEffect(isEnableBlurEffect: Boolean) {
+ this.isEnableBlurEffect.value = isEnableBlurEffect
+ postInvalidate()
+ }
+
+ override fun setItemOffsetPercent(itemOffsetPercent: Float) {
+ this.itemOffsetPercent = itemOffsetPercent
+ postInvalidate()
+ }
+
+ companion object {
+
+ private const val TAG = "LyricViewX"
+
+ // 时间线持续时间
+ private const val TIMELINE_KEEP_TIME = 3 * DateUtils.SECOND_IN_MILLIS
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt b/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt
new file mode 100644
index 0000000..9b81331
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/ReadyHelper.kt
@@ -0,0 +1,51 @@
+package com.dirror.lyricviewx
+
+import androidx.annotation.IntDef
+
+const val STATE_CREATED = 1
+const val STATE_INITIALIZING = 2
+const val STATE_INITIALIZED = 3
+const val STATE_ERROR = 4
+
+@IntDef(
+ STATE_CREATED,
+ STATE_INITIALIZING,
+ STATE_INITIALIZED,
+ STATE_ERROR
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class ReadyState
+
+/**
+ * 简单的状态机,根据 [readyState] 的状态决定当前任务的执行或延后与否
+ */
+open class ReadyHelper {
+ private var readyCallback: (Boolean) -> Unit = {}
+
+ @ReadyState
+ var readyState: Int = STATE_CREATED
+ set(value) {
+ if (field == value) return
+ when (value) {
+ STATE_INITIALIZED,
+ STATE_ERROR -> synchronized(readyCallback) {
+ field = value
+ readyCallback.invoke(value != STATE_ERROR)
+ }
+ else -> field = value
+ }
+ }
+
+ fun whenReady(performAction: (Boolean) -> Unit): Boolean {
+ return when (readyState) {
+ STATE_CREATED, STATE_INITIALIZING -> {
+ readyCallback = performAction
+ false
+ }
+ else -> {
+ performAction(readyState != STATE_ERROR)
+ true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/SmoothInterpolator.kt b/app/src/main/java/com/dirror/lyricviewx/SmoothInterpolator.kt
new file mode 100644
index 0000000..2ad4a4b
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/SmoothInterpolator.kt
@@ -0,0 +1,20 @@
+package com.dirror.lyricviewx
+
+import android.animation.TimeInterpolator
+import kotlin.math.pow
+
+/**
+ * Smooth 插值器
+ * @author Moriafly
+ */
+@Deprecated("过时")
+class SmoothInterpolator: TimeInterpolator {
+ override fun getInterpolation(input: Float): Float {
+ val a = 1.11571230005336
+ val b = -1.99852071205059
+ val c = 0.272428743837376
+ val d = -1.15835562067601E-05
+ return ((a - d) / (1.0 + (input.toDouble() / c).pow(b)) + d).toFloat()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt b/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt
new file mode 100644
index 0000000..f5a8558
--- /dev/null
+++ b/app/src/main/java/com/dirror/lyricviewx/extension/BlurMaskFilterExt.kt
@@ -0,0 +1,15 @@
+package com.dirror.lyricviewx.extension
+
+import android.graphics.BlurMaskFilter
+import android.util.SparseArray
+
+class BlurMaskFilterExt {
+ private val maskFilterCache = SparseArray()
+
+ fun get(radius: Int): BlurMaskFilter? {
+ if (radius == 0 || radius > 25) return null
+
+ return maskFilterCache[radius] ?: BlurMaskFilter(radius.toFloat(), BlurMaskFilter.Blur.NORMAL)
+ .also { maskFilterCache.put(radius, it) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/MediaPlayer.java b/app/src/main/java/com/muqingbfq/MediaPlayer.java
new file mode 100644
index 0000000..efcacb0
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/MediaPlayer.java
@@ -0,0 +1,138 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+import com.muqingbfq.api.url;
+import com.muqingbfq.fragment.mp3;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class MediaPlayer extends android.media.MediaPlayer {
+ public MediaPlayer() {
+ this.setOnCompletionListener(mediaPlayer -> {
+ if (!home.db.view.isShown()) {
+ home.db.view.setVisibility(View.VISIBLE);
+ }
+ int i = bfqkz.getmti(bfqkz.ms);
+ bfqkz.xm = bfqkz.list.get(i);
+ new Thread() {
+ @Override
+ public void run() {
+ super.run();
+ bfqkz.mp3(url.hq(bfqkz.xm));
+ }
+ }.start();
+ });
+ this.setOnErrorListener((mediaPlayer, i, i1) -> {
+ bfqkz.list.remove(bfqkz.xm);
+ return false;
+ });
+ resumeTimer();
+ }
+
+ @Override
+ public void pause() throws IllegalStateException {
+ if (isPlaying()) {
+ super.pause();
+ //暂停
+ if (bfq.kg != null) {
+ bfq.kg.setImageResource(R.drawable.zt);
+ }
+ home.db.txa.setImageResource(R.drawable.zt);
+ bfqkz.updateNotification();
+ }
+ }
+
+ public Timer timer;
+ public TimerTask timerTask;
+ public void pauseTimer() {
+ if (timer != null) {
+ timer.cancel();
+ }
+ }
+ public void resumeTimer() {
+ timer = new Timer();//定时器
+ timerTask = new TimerTask() {
+ @Override
+ public void run() {
+ if (bfqkz.mt.isPlaying() && bfq.getVisibility()) {
+ int currentPosition = bfqkz.mt.getCurrentPosition();
+ bfq.tdt.setProgress(currentPosition);
+ bfq.lrcView.updateTime(currentPosition, true);
+ }
+ }
+ };
+ timer.scheduleAtFixedRate(timerTask, 0, 500);
+ }
+
+ @Override
+ public void start() throws IllegalStateException {
+ super.start();
+ if (bfqkz.xm == null) {
+ bfq_an.xyq();
+ return;
+ }
+ //开始
+ if (bfq.kg != null) {
+ bfq.kg.setImageResource(R.drawable.bf);
+ }
+ home.db.txa.setImageResource(R.drawable.bf);
+ bfqkz.updateNotification();
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ @Override
+ public void setDataSource(String path) throws IOException, IllegalArgumentException, IllegalStateException, SecurityException {
+ super.setDataSource(path);
+ prepare();
+ bfqkz.tdt_max = getDuration();
+ bfqkz.tdt_wz = getCurrentPosition();
+ Glide.with(main.context)
+ .asBitmap()
+ .load(bfqkz.xm.picurl)
+ .addListener(new RequestListener() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model,
+ @NonNull Target target, boolean isFirstResource) {
+ bfqkz.notify.setBitmap(null);
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(@NonNull Bitmap bitmap, @NonNull Object model, Target target,
+ @NonNull DataSource dataSource, boolean isFirstResource) {
+ bfqkz.notify.setBitmap(bitmap);
+ return false;
+ }
+ })
+ .submit();
+ start();
+ main.handler.post(() -> {
+ if (bfq.name != null) {
+ bfq.tdt.setMax((int) bfqkz.tdt_max);
+ bfq.tdt.setProgress((int) bfqkz.tdt_wz);
+ bfq.time_a.setText(bfq_an.getTime(bfqkz.tdt_max));
+ bfq.name.setText(bfqkz.xm.name);
+ bfq.zz.setText(bfqkz.xm.zz);
+ bfq_an.islike(bfq.like.getContext());
+ }
+ home.db.name.setText(bfqkz.xm.name);
+ home.db.zz.setText(bfqkz.xm.zz);
+ if (mp3.lbspq != null) {
+ mp3.lbspq.notifyDataSetChanged();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java b/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java
new file mode 100644
index 0000000..b8af591
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/MyButtonClickReceiver.java
@@ -0,0 +1,155 @@
+package com.muqingbfq;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class MyButtonClickReceiver extends BroadcastReceiver {
+ private Timer timer = new Timer();
+ private static int clickCount;
+
+ public MyButtonClickReceiver() {
+ super();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
+ //蓝牙断开
+ if (bluetoothState == BluetoothAdapter.STATE_DISCONNECTED) {
+ receiverPause();
+ }
+ return;
+ }
+ if (action.equals("android.intent.action.HEADSET_PLUG")) {
+ if (intent.hasExtra("state")) {
+ if (intent.getIntExtra("state", 2) == 0) {
+ //拔出
+ if (bfqkz.mt.isPlaying()) {
+ receiverPause();
+ }
+ } else if (intent.getIntExtra("state", 2) == 1) {
+ receiverPlay();
+ //插入
+ }
+ }
+ return;
+ }
+ if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
+ KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ clickCount = clickCount + 1;
+ if (clickCount == 1) {
+ HeadsetTimerTask headsetTimerTask = new HeadsetTimerTask();
+ timer.schedule(headsetTimerTask, 500);
+ }
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ handler(2);
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ handler(3);
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ handler(4);
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ handler(5);
+ }
+ return;
+ }
+
+ switch (action) {
+ case "kg":
+ playOrPause();
+ break;
+ case "syq":
+ bfq_an.syq();
+ break;
+ case "xyq":
+ bfq_an.xyq();
+ break;
+ }
+ // 处理按钮点击事件的逻辑
+ }
+
+ class HeadsetTimerTask extends TimerTask {
+ @Override
+ public void run() {
+ try {
+ if (clickCount == 1) {
+ handler(1);
+ } else if (clickCount == 2) {
+ handler(2);
+ } else if (clickCount >= 3) {
+ handler(3);
+ }
+ clickCount = 0;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ private void handler(int a) {
+ switch (a) {
+ case 1:
+ playOrPause();
+ break;
+ case 2:
+ playNext();
+ break;
+ case 3:
+ playPrevious();
+ break;
+ case 4:
+ receiverPause();
+ break;
+ case 5:
+ receiverPlay();
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ // * 对蓝牙 播放
+ public void receiverPlay() {
+ bfqkz.mt.start();
+ }
+
+ // * 对蓝牙 暂停
+ public void receiverPause() {
+ bfqkz.mt.pause();
+ }
+
+ /**
+ * 对蓝牙 播放-暂停
+ */
+ public static void playOrPause() {
+// gj.sc(isMusicServiceBound);
+ // 播放/暂停按钮点击事件 if (isMusicServiceBound)
+ if (bfqkz.mt.isPlaying()) {
+ bfqkz.mt.pause();
+ } else {
+ bfqkz.mt.start();
+ }
+ }
+
+ /**
+ * 对蓝牙 下一首
+ */
+ public void playNext() {
+ bfq_an.xyq();
+ }
+
+ /**
+ * 对蓝牙 上一首
+ */
+ public void playPrevious() {
+ bfq_an.syq();
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/activity_about_software.java b/app/src/main/java/com/muqingbfq/activity_about_software.java
new file mode 100644
index 0000000..6ada786
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/activity_about_software.java
@@ -0,0 +1,36 @@
+package com.muqingbfq;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class activity_about_software extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about_software);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ try {
+ String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
+ toolbar.setSubtitle(versionName + " Base");
+ } catch (PackageManager.NameNotFoundException e) {
+ yc.start(this, e);
+ }
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/activity_search.java b/app/src/main/java/com/muqingbfq/activity_search.java
new file mode 100644
index 0000000..bf7eb9b
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/activity_search.java
@@ -0,0 +1,270 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.muqingbfq.fragment.search;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class activity_search extends AppCompatActivity {
+ private EditText editText;
+ private ArrayAdapter adapter;
+
+ private JSONObject json = new JSONObject();
+ private List json_list = new ArrayList<>();
+ private final List list = new ArrayList<>();
+ ListView listPopupWindow;
+ public static AppCompatActivity appCompatActivity;
+
+ @SuppressLint({"RestrictedApi", "NotifyDataSetChanged"})
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_search);
+ appCompatActivity = this;
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ RecyclerView recyclerView = findViewById(R.id.list_recycler);
+ SearchRecordAdapter recordAdapter = new SearchRecordAdapter();
+ recyclerView.setAdapter(recordAdapter);
+
+ editText = findViewById(R.id.editview);
+ editText.setOnEditorActionListener((v, actionId, event) -> {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ String str = v.getText().toString();
+ if (!str.equals("")) {
+// 退出activity并返回str数据
+ start(str);
+ }
+ }
+ return false;
+ });
+ findViewById(R.id.deleat).setOnClickListener(v -> new MaterialAlertDialogBuilder(v.getContext())
+ .setTitle("删除")
+ .setMessage("清空历史记录?")
+ .setNegativeButton("取消", null)
+ .setPositiveButton("确定", (dialogInterface, i) -> {
+ wj.sc(wj.filesdri + wj.lishi_json);
+ json = new JSONObject();
+ json_list.clear();
+ recordAdapter.notifyDataSetChanged();
+ findViewById(R.id.xxbj1).setVisibility(View.GONE);
+ })
+ .show());
+ listPopupWindow = findViewById(R.id.search_recycler);
+
+ adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list);
+ listPopupWindow.setAdapter(adapter);
+
+ //设置项点击监听
+ listPopupWindow.setOnItemClickListener((adapterView, view, i, l) -> {
+ editText.clearFocus();
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ View v = getWindow().peekDecorView();
+ if (null != v) {
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ editText.setText(list.get(i));//把选择的选项内容展示在EditText上
+ dismiss();//如果已经选择了,隐藏起来
+ start(editText.getText().toString());
+
+ });
+ editText.setOnFocusChangeListener((view, b) -> {
+ if (b) {
+ dismiss();
+ }
+ });
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ list.clear();
+ if (s.length() < 1) {
+ list.clear();
+ adapter.notifyDataSetChanged();
+ editText.clearFocus();
+ dismiss();
+ return;
+ }
+ if (!editText.hasFocus()) {
+ dismiss();
+ return;
+ }
+ listPopupWindow.setVisibility(View.VISIBLE);
+ new Thread() {
+ @Override
+ public void run() {
+ String hq = wl.hq("/search/suggest?keywords=" + s + "&type=mobile");
+ try {
+ JSONArray jsonArray = new JSONObject(hq).getJSONObject("result")
+ .getJSONArray("allMatch");
+ int length = jsonArray.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ String keyword = jsonObject.getString("keyword");
+ list.add(keyword);
+ }
+ main.handler.post(() -> adapter.notifyDataSetChanged());
+ } catch (Exception e) {
+ gj.ts(activity_search.this, e);
+ }
+ super.run();
+ }
+ }.start();
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+ fragmentManager = getSupportFragmentManager();
+ fragmentTransaction = fragmentManager.beginTransaction();
+ }
+
+ public void dismiss() {
+ listPopupWindow.setVisibility(View.GONE);
+ }
+
+ private void addSearchRecord(String name) {
+ try {
+ if (!json.has("list")) {
+ json.put("list", new JSONArray());
+ }
+ if (!json_list.contains(name)) {
+ json_list.add(name);
+ JSONObject record = new JSONObject();
+ record.put("name", name);
+ json.getJSONArray("list").put(record);
+ wj.xrwb(wj.filesdri + wj.lishi_json, json.toString());
+ adapter.notifyDataSetChanged();
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.home, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == android.R.id.home) {
+ finish();
+ } else if (itemId == R.id.menu_search) {
+ start(editText.getText().toString());
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ FragmentManager fragmentManager;
+ FragmentTransaction fragmentTransaction;
+
+ com.muqingbfq.fragment.search search;
+ public void start(String name) {
+ dismiss();
+ if (name.equals("")) {
+ return;
+ }
+ if (search == null) {
+ search = new search(name);
+ }
+ if (!search.isVisible()) {
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.search_fragment, search)
+ .addToBackStack(null).commit();
+ } else {
+ search.setStart(name);
+ }
+ addSearchRecord(name);
+ }
+
+ class SearchRecordAdapter extends RecyclerView.Adapter {
+ public SearchRecordAdapter() {
+ String dqwb = wj.dqwb(wj.filesdri + wj.lishi_json);
+ if (dqwb != null) {
+ try {
+ json = new JSONObject(dqwb);
+ JSONArray list1 = json.getJSONArray("list");
+ int length = list1.length();
+ for (int i = length - 1; i >= 0; i--) {
+ json_list.add(list1.
+ getJSONObject(i).getString("name"));
+ }
+ } catch (JSONException e) {
+ yc.start(activity_search.this, e);
+ }
+ }
+ if (json_list.isEmpty()) {
+ findViewById(R.id.xxbj1).setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ String keyword = json_list.get(position);
+ holder.recordTextView.setText(keyword);
+ holder.recordTextView.setOnClickListener(v -> {
+ editText.setText(keyword);
+ start(keyword);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return json_list.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ TextView recordTextView;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ recordTextView = itemView.findViewById(android.R.id.text1);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/api/playlist.java b/app/src/main/java/com/muqingbfq/api/playlist.java
new file mode 100644
index 0000000..b608ef5
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/api/playlist.java
@@ -0,0 +1,124 @@
+package com.muqingbfq.api;
+
+import android.annotation.SuppressLint;
+
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.fragment.gd;
+import com.muqingbfq.fragment.mp3;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.xm;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class playlist extends Thread {
+ public static final String api = "/playlist/track/all?id=";
+ private String uid;
+
+ public playlist(String uid) {
+ this.uid = uid;
+ start();
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ public static boolean hq(List list, String uid) {
+ list.clear();
+ try {
+ String hq;
+ if (wj.cz(wj.gd + uid)) {
+ hq = wj.dqwb(wj.gd + uid);
+ } else {
+ hq = wl.hq(api + uid + "&limit=30");
+ }
+ JSONObject json = new JSONObject(hq);
+ JSONArray songs = json.getJSONArray("songs");
+ int length = songs.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject jsonObject = songs.getJSONObject(i);
+ String id = jsonObject.getString("id");
+ String name = jsonObject.getString("name");
+
+ JSONObject al = jsonObject.getJSONObject("al");
+ JSONArray ar = jsonObject.getJSONArray("ar");
+ StringBuilder zz = new StringBuilder();
+ int length_a = ar.length();
+ for (int j = 0; j < length_a; j++) {
+ zz.append(ar.getJSONObject(j).getString("name"))
+ .append("/");
+ }
+ zz.append("-").append(al.getString("name"));
+ String picUrl = al.getString("picUrl");
+ list.add(new xm(id, name, zz.toString(), picUrl));
+ }
+// main.handler.post(new mp3.lbspq_sx());
+ return true;
+ } catch (Exception e) {
+ gj.sc("失败的错误 " + e);
+ }
+ return false;
+ }
+
+ public static void hq_like(List list) {
+ list.clear();
+ try {
+ JSONObject json = gd.like;
+ for (Iterator it = json.keys(); it.hasNext(); ) {
+ String id = it.next();
+ JSONObject jsonObject = json.getJSONObject(id);
+ String name = jsonObject.getString("name");
+ String zz = jsonObject.getString("zz");
+ String picUrl = jsonObject.getString("picUrl");
+ list.add(new xm(id, name, zz, picUrl));
+ }
+ main.handler.post(new mp3.lbspq_sx());
+ } catch (Exception e) {
+ gj.sc("失败的错误 " + e);
+ }
+ }
+
+ public static void hq_xz(List list) {
+ list.clear();
+ try {
+ JSONArray json = new JSONObject(wj.dqwb(wj.mp3_xz))
+ .getJSONArray("songs");
+ int length = json.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject jsonObject = json.getJSONObject(i);
+ String id = jsonObject.getString("id");
+ String name = jsonObject.getString("name");
+ String zz = jsonObject.getString("zz");
+ String picUrl = jsonObject.getString("picUrl");
+ list.add(new xm(id, name, zz, picUrl));
+ }
+// main.handler.post(new mp3.lbspq_sx());
+ } catch (Exception e) {
+ gj.sc("失败的错误 " + e);
+ wj.sc(wj.mp3_xz);
+ }
+ }
+
+ @Override
+ public void run() {
+ super.run();
+/* if (uid.equals(wj.mp3_xz)) {
+ playlist.hq_xz(mp3.list);
+ } else if (uid.equals(wj.mp3_like)) {
+ playlist.hq_like(mp3
+ .list);
+ } else {
+ playlist.hq(mp3.list, uid);
+ }
+ if (bfqkz.list == null || bfqkz.list.isEmpty()) {
+ int size = mp3.list.size();
+ for (int i = 0; i < size; i++) {
+ bfqkz.list.add(mp3.list.get(i));
+ }
+ }*/
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/api/resource.java b/app/src/main/java/com/muqingbfq/api/resource.java
new file mode 100644
index 0000000..d87699d
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/api/resource.java
@@ -0,0 +1,107 @@
+package com.muqingbfq.api;
+
+import com.muqingbfq.R;
+import com.muqingbfq.main;
+import com.muqingbfq.start;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+import java.util.List;
+import com.muqingbfq.xm;
+public class resource {
+
+ public static void recommend(List list) {
+ String hq;
+ JSONObject json;
+ try {
+ if (wj.cz(wj.gd_json)&& start.time>System.currentTimeMillis()-3600000) {
+ hq = wj.dqwb(wj.gd_json);
+ json = new JSONObject(hq);
+ } else {
+ hq = wl.hq("/recommend/resource?cookie="+wl.Cookie);
+ if (hq == null && wj.cz(wj.gd_json)) {
+ hq = wj.dqwb(wj.gd_json);
+ json = new JSONObject(hq);
+ }
+ json = new JSONObject(hq);
+ if (json.getInt("code") == 200) {
+ wj.xrwb(wj.gd_json, hq);
+ start.time = System.currentTimeMillis();
+ main.edit.putLong(main.Time, start.time);
+ main.edit.commit();
+ }
+ }
+ JSONArray recommend = json.getJSONArray("recommend");
+ int length = recommend.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject jsonObject = recommend.getJSONObject(i);
+ add(jsonObject, list);
+ }
+ } catch (Exception e) {
+ gj.sc("resource tuijian" + e);
+ }
+ }
+
+ public static void 排行榜(List list) {
+ String hq;
+ try {
+ if (wj.cz(wj.gd_phb)) {
+ hq = wj.dqwb(wj.gd_phb);
+ } else {
+ hq = wl.hq("/toplist");
+ if (hq == null) {
+ return;
+ }
+ wj.xrwb(wj.gd_phb, hq);
+ }
+ JSONObject jsonObject = new JSONObject(hq);
+ if (jsonObject.getInt("code") == 200) {
+ JSONArray list_array = jsonObject.getJSONArray("list");
+ int length = list_array.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject get = list_array.getJSONObject(i);
+ String id = get.getString("id");
+ String name = get.getString("name") + "\n" + get.getString("description");
+ boolean cz = wj.cz(wj.gd + id);
+ String coverImgUrl = get.getString("coverImgUrl");
+ list.add(new xm(id, name, coverImgUrl, cz));
+ }
+ }
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+
+ public static void 下载(List list) {
+// list.add(new xm("hc.json", "缓存", R.drawable.icon, true));
+ list.add(new xm("mp3_like.json", "喜欢", R.mipmap.like, true));
+ list.add(new xm("mp3_xz.json", "下载", R.drawable.icon, true));
+ try {
+ // JSONArray date = jsonObject.getJSONArray("");
+ JSONObject date = new JSONObject(wj.dqwb(wj.gd_xz));
+ for (Iterator it = date.keys(); it.hasNext(); ) {
+ String id = it.next();
+ boolean cz = wj.cz(wj.gd + id);
+ JSONObject jsonObject = date.getJSONObject(id);
+ String name = jsonObject.getString("name");
+ String picUrl = jsonObject.getString("picUrl");
+ list.add(new xm(id, name, picUrl, cz));
+ }
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+
+ private static void add(JSONObject jsonObject, List list) throws Exception {
+ String id = jsonObject.getString("id");
+ boolean cz = wj.cz(wj.gd + id);
+ String name = jsonObject.getString("name");
+ String picUrl = jsonObject.getString("picUrl");
+ list.add(new xm(id, name, picUrl, cz));
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/api/url.java b/app/src/main/java/com/muqingbfq/api/url.java
new file mode 100644
index 0000000..40776b4
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/api/url.java
@@ -0,0 +1,99 @@
+package com.muqingbfq.api;
+
+import android.view.View;
+
+import com.muqingbfq.bfq;
+import com.muqingbfq.home;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.xm;
+import com.muqingbfq.yc;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class url extends Thread {
+ public static String api = "/song/url/v1";
+ xm x;
+
+ public url(xm x) {
+ this.x = x;
+ start();
+ }
+
+ public static String hq(xm x) {
+ if (bfq.getVisibility() && bfq.lrcView != null && bfq.lrcView.getVisibility() == View.VISIBLE) {
+ gc(x.id);
+ } else {
+ lrc = null;
+ }
+ try {
+ if (wj.cz(wj.mp3 + x.id)) {
+ return wj.mp3 + x.id;
+ }
+ String level = "standard";
+ if (gj.isWiFiConnected()) {
+ level = "exhigh";
+ }
+ String hq = wl.hq(api + "?id=" + x.id + "&level=" +
+ level + "&cookie=" + wl.Cookie);
+ gj.sc(hq);
+ if (hq == null) {
+ return null;
+ }
+ JSONObject json = new JSONObject(hq);
+ JSONArray data = json.getJSONArray("data");
+ JSONObject jsonObject = data.getJSONObject(0);
+
+ String url = jsonObject.getString("url");
+ if (wl.xz(url, x)) {
+ url = wj.mp3 + x.id;
+ }
+ return url;
+ } catch (JSONException e) {
+ yc.start("url hq :" + e);
+ }
+ return null;
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ com.muqingbfq.bfqkz.mp3(hq(x));
+ }
+
+
+ public static String lrc, tlyric;
+
+ public static void gc(String id) {
+ lrc = null;
+ tlyric = null;
+ JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject = new JSONObject(wl.hq("/lyric?id=" + id));
+ lrc = jsonObject.getJSONObject("lrc").getString("lyric");
+ } catch (JSONException e) {
+ gj.sc("url gc(int id) lrc: " + e);
+ }
+ try {
+ tlyric = jsonObject.getJSONObject("tlyric").getString("lyric");
+ } catch (JSONException e) {
+ gj.sc("url gc(int id) tlyric: " + e);
+ }
+ bfq.lrcView.loadLyric(lrc, tlyric);
+ }
+
+ public static String picurl(String id) {
+ String hq = wl.hq("/song/detail?ids=" + id);
+ try {
+ return new JSONObject(hq).getJSONArray("songs").getJSONObject(0)
+ .getJSONObject("al").getString("picUrl");
+ } catch (Exception e) {
+ yc.start(main.context, e);
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/bfq.java b/app/src/main/java/com/muqingbfq/bfq.java
new file mode 100644
index 0000000..5358851
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/bfq.java
@@ -0,0 +1,196 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import com.bumptech.glide.Glide;
+
+import org.json.JSONObject;
+
+public class bfq extends AppCompatActivity {
+ @SuppressLint("StaticFieldLeak")
+ public static SeekBar tdt;
+ @SuppressLint("StaticFieldLeak")
+ public static TextView name, zz, time_a, time_b;
+ @SuppressLint("StaticFieldLeak")
+ public static ImageView tx;
+ @SuppressLint("StaticFieldLeak")
+ public static ImageView kg, syq, xyq, like;
+ public static com.dirror.lyricviewx.LyricViewX lrcView;
+ @SuppressLint("ResourceType")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ FrameLayout frameLayout = new FrameLayout(this);
+ // 设置 FrameLayout 的布局参数(可以根据自己的需要进行设置)
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, // 宽度为 Match Parent
+ FrameLayout.LayoutParams.MATCH_PARENT); // 高度为 Match Parent
+ frameLayout.setLayoutParams(params);
+ frameLayout.setId(1);
+ setContentView(frameLayout);
+ getSupportFragmentManager().beginTransaction()
+ .add(frameLayout.getId(), new fragment(this))
+ .commit();
+/* TypedValue typedValue = new TypedValue();
+ getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true);
+ // 设置背景颜色
+ bj = typedValue.data;*/
+ }
+ @SuppressLint("StaticFieldLeak")
+ public static View inflate;
+ private static AppCompatActivity context;
+ public static Bitmap bitmap;
+ public static class fragment extends Fragment {
+
+ public fragment(AppCompatActivity context) {
+ bfq.context = context;
+ }
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (inflate != null) {
+ tx.setImageBitmap(bitmap);
+ return inflate;
+ }
+ inflate = inflater.inflate(R.layout.fragment_bfq, container, false);
+ lrcView = inflate.findViewById(R.id.gc);
+ Toolbar toolbar = inflate.findViewById(R.id.toolbar);
+ name = inflate.findViewById(R.id.name);
+ zz = inflate.findViewById(R.id.zz);
+ kg = inflate.findViewById(R.id.kg);
+ xyq = inflate.findViewById(R.id.xyq);
+ syq = inflate.findViewById(R.id.syq);
+ tx = inflate.findViewById(R.id.mttx);
+ tdt = inflate.findViewById(R.id.tdt);
+ time_a = inflate.findViewById(R.id.time_a);
+ time_b = inflate.findViewById(R.id.time_b);
+
+// lrcView.setIsEnableBlurEffect(true);
+ View kp = inflate.findViewById(R.id.kp1);
+ kp.setOnClickListener(v -> {
+ if (lrcView != null) {
+ v.setVisibility(View.GONE);
+ lrcView.setVisibility(View.VISIBLE);
+ if (com.muqingbfq.api.url.lrc == null) {
+ new Thread() {
+ @Override
+ public void run() {
+ super.run();
+ com.muqingbfq.api.url.gc(bfqkz.xm.id);
+ }
+ }.start();
+ }
+ }
+ });
+ toolbar.setNavigationIcon(R.drawable.end);
+ toolbar.setNavigationOnClickListener(view1 -> context.finish());
+ toolbar.inflateMenu(R.menu.bfq);
+ toolbar.setOnMenuItemClickListener(item -> {
+ if (item.getItemId() == R.id.fx) {
+ com.muqingbfq.mq.gj.fx(context,
+ "音乐名称:" + name.getText().toString() +
+ "\n 作者:" + zz.getText().toString() +
+ "\n 链接:https://music.163.com/#/song?id=" + bfqkz.id);
+ }
+ return false;
+ });
+ lrcView.setDraggable(true, (time) -> {
+ com.muqingbfq.bfqkz.mt.seekTo(Math.toIntExact(time));
+ return true;
+ });
+ lrcView.setOnSingerClickListener(() -> {
+ lrcView.setVisibility(View.GONE);
+ kp.setVisibility(View.VISIBLE);
+ });
+ inflate.findViewById(R.id.layout).setOnClickListener(view1 -> {
+ lrcView.setVisibility(View.GONE);
+ kp.setVisibility(View.VISIBLE);
+ });
+ inflate.findViewById(R.id.bfq_list_mp3).
+ setOnClickListener(view1 -> com.muqingbfq.fragment.bflb_db.start(context));
+
+ bfq_an.kz kz = new bfq_an.kz();
+ kg.setOnClickListener(kz);
+ syq.setOnClickListener(kz);
+ xyq.setOnClickListener(kz);
+ like = inflate.findViewById(R.id.like);
+ ImageView control = inflate.findViewById(R.id.control);
+ control.setOnClickListener(new bfq_an.control(control));
+ UI(inflate);
+ return inflate;
+ }
+ private void UI(View view) {
+// tdt.getProgressDrawable().
+// setColorFilter(ContextCompat.getColor(this, R.color.text_tm), PorterDuff.Mode.MULTIPLY);
+// tdt.getThumb().
+// setColorFilter(ContextCompat.getColor(this, R.color.text), PorterDuff.Mode.SRC_IN);
+ tdt.setOnSeekBarChangeListener(new bfq_an.tdt());
+
+ like.setOnClickListener(view1 -> {
+ try {
+ if (bfqkz.like_bool) {
+ like.setImageTintList(ContextCompat.getColorStateList(view.getContext(), R.color.text));
+ com.muqingbfq.fragment.gd.like.remove(String.valueOf(bfqkz.xm.id));
+ } else {
+ like.setImageTintList(ContextCompat.
+ getColorStateList(view.getContext(), android.R.color.holo_red_dark));
+ JSONObject json = new JSONObject();
+ json.put("name", bfqkz.xm.name);
+ json.put("zz", bfqkz.xm.zz);
+ json.put("picUrl", bfqkz.xm.picurl);
+ com.muqingbfq.fragment.gd.like.put(String.valueOf(bfqkz.xm.id), json);
+ }
+ com.muqingbfq.mq.wj.xrwb(com.muqingbfq.mq.wj.mp3_like,
+ com.muqingbfq.fragment.gd.like.toString());
+ bfqkz.like_bool = !bfqkz.like_bool;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ if (bfqkz.xm != null) {
+ xm xm = bfqkz.xm;
+ name.setText(xm.name);
+ zz.setText(xm.zz);
+ time_a.setText(bfq_an.getTime(bfqkz.tdt_max));
+ tdt.setMax((int) bfqkz.tdt_max);
+ if (bfqkz.mt.isPlaying()) {
+ kg.setImageResource(R.drawable.bf);
+ }
+ bfq_an.islike(context);
+ Glide.with(context).load(xm.picurl)
+ .placeholder(R.drawable.icon)
+ .into(tx);
+ }
+ }
+ }
+
+ public static void start(AppCompatActivity context) {
+ Intent intent = new Intent(context, bfq.class);
+ context.startActivity(intent);
+// home.dialog.show(context.getSupportFragmentManager(), "bfq");
+ }
+
+ public static boolean getVisibility() {
+ if (inflate == null) {
+ return false;
+ }
+ return inflate.isShown();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/bfq_an.java b/app/src/main/java/com/muqingbfq/bfq_an.java
new file mode 100644
index 0000000..85dbeb4
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/bfq_an.java
@@ -0,0 +1,146 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import androidx.core.content.ContextCompat;
+
+import com.muqingbfq.api.url;
+import com.muqingbfq.fragment.gd;
+import com.muqingbfq.fragment.mp3;
+import com.muqingbfq.mq.gj;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class bfq_an {
+ public static class kz implements View.OnClickListener {
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+ if (id == R.id.kg) {
+ MyButtonClickReceiver.playOrPause();
+ } else if (id == R.id.syq) {
+ syq();
+ } else if (id == R.id.xyq) {
+ xyq();
+ }
+ }
+ }
+
+ public static void syq() {
+ bfqkz.mt.pause();
+ int i = bfqkz.list.indexOf(bfqkz.xm) - 1;
+ if (i < 0) {
+ i = 0;
+ }
+ bfqkz.xm = bfqkz.list.get(i);
+ new url(bfqkz.xm);
+ }
+
+ public static void xyq() {
+ bfqkz.mt.pause();
+ int ms = bfqkz.ms;
+ if (bfqkz.ms == 0) {
+ ms = 1;
+ }
+ bfqkz.xm = bfqkz.list.get(bfqkz.getmti(ms));
+ new url(bfqkz.xm);
+ }
+
+ public static class tdt implements SeekBar.OnSeekBarChangeListener {
+ // SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm");
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ bfq.time_b.setText(getTime(progress));
+
+// bfq.time_b.setText(simpleDateFormat.format(new Date(progress)));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // 暂停播放
+ bfqkz.mt.pauseTimer();
+// bfqkz.mt.pause();
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // 播放音乐到指定位置
+ bfqkz.mt.seekTo(seekBar.getProgress());
+ bfqkz.mt.resumeTimer();
+// bfqkz.mt.start();
+ }
+ }
+
+ public static class control implements View.OnClickListener {
+ public control(ImageView imageView) {
+ switch (bfqkz.ms) {
+ case 0:
+ imageView.setImageResource(R.drawable.mt_xh);
+ break;
+ case 1:
+ imageView.setImageResource(R.drawable.mt_sx);
+ break;
+ case 2:
+ imageView.setImageResource(R.drawable.mt_sj);
+ break;
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ ImageView imageView = (ImageView) v;
+ switch (bfqkz.ms) {
+ case 0:
+ bfqkz.ms = 1;
+ imageView.setImageResource(R.drawable.mt_sx);
+// 顺序
+ break;
+ case 1:
+ bfqkz.ms = 2;
+ imageView.setImageResource(R.drawable.mt_sj);
+// 随机
+ break;
+ case 2:
+ bfqkz.ms = 0;
+ imageView.setImageResource(R.drawable.mt_xh);
+// 循环
+ break;
+ }
+ main.edit.putInt("ms", bfqkz.ms);
+ main.edit.commit();
+// imageView.setImageDrawable();
+ }
+ }
+
+ @SuppressLint("SimpleDateFormat")
+ static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
+ public static String getTime(long time) {
+ return simpleDateFormat.format(new Date(time));
+ }
+
+ public static void UI(boolean bool) {
+ if (bfq.getVisibility()) {
+ bfq.xyq.setEnabled(bool);
+ bfq.syq.setEnabled(bool);
+ bfq.kg.setEnabled(bool);
+ bfq.tdt.setEnabled(bool);
+ }
+ }
+ public static void islike(Context context) {
+ try {
+ gd.like.getJSONObject(String.valueOf(bfqkz.xm.id));
+ bfq.like.setImageTintList(ContextCompat.
+ getColorStateList(context, android.R.color.holo_red_dark));
+ bfqkz.like_bool = true;
+ } catch (Exception e) {
+ bfq.like.setImageTintList(ContextCompat.getColorStateList(context, R.color.text));
+ gj.sc("bfq_an islike() :" + e);
+ bfqkz.like_bool = false;
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/bfqkz.java b/app/src/main/java/com/muqingbfq/bfqkz.java
new file mode 100644
index 0000000..aad4750
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/bfqkz.java
@@ -0,0 +1,131 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.MediaBrowserServiceCompat;
+
+import com.muqingbfq.api.url;
+import com.muqingbfq.mq.gj;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class bfqkz extends MediaBrowserServiceCompat {
+ public static com.muqingbfq.MediaPlayer mt = new com.muqingbfq.MediaPlayer();
+ public static String id;
+ public static List list = new ArrayList<>();
+ public static long tdt_max, tdt_wz;
+ public static int ms;
+ // 0 循环 1 顺序 2 随机
+ public static xm xm;
+ public static boolean like_bool;
+ @SuppressLint("StaticFieldLeak")
+ public static com.muqingbfq.mq.NotificationManagerCompat notify;
+ public static int getmti(int s) {
+ int i = bfqkz.list.indexOf(xm);
+ if (s == 1) {
+ i = bfqkz.list.indexOf(xm) + 1;
+ if (i >= bfqkz.list.size()) {
+ i = 0;
+ }
+ } else if (s == 2) {
+ i = new Random().nextInt(bfqkz.list.size());
+ }
+ return i;
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ public static void mp3(String id) {
+ try {
+ if (id == null) {
+ return;
+ }
+ if (xm.picurl == null || xm.picurl.equals("")) {
+ xm.picurl = url.picurl(xm.id);
+ }
+ mt.reset();
+ mt.setDataSource(id);
+ } catch (Exception e) {
+ gj.sc("bfqkz mp3(String id) :" + e);
+ }
+ }
+ public static MediaSessionCompat mSession;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSession = new MediaSessionCompat(this,"MusicService");
+ mSession.setCallback(new MediaSessionCompat.Callback() {
+ @Override
+ public void onPlay() {
+ mt.start();
+ // 处理播放音乐逻辑
+ }
+
+ @Override
+ public void onPause() {
+ // 处理暂停音乐逻辑
+ mt.pause();
+ }
+
+ @Override
+ public void onSkipToNext() {
+ // 处理切换到下一首音乐逻辑
+ }
+ @Override
+ public void onSkipToPrevious() {
+ // 处理切换到上一首音乐逻辑
+ }
+ });//设置回调
+/* Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(new ComponentName(this, start.class));//用ComponentName得到class对象
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);// 关键的一步,设置启动模式,两种情况
+ MediaButtonReceiver.handleIntent(mSession,intent);*/
+
+ MediaMetadataCompat build = new MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "歌手名称")
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "专辑名称")
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "歌曲名称")
+ .build();
+ mSession.setMetadata(build);
+ mSession.setActive(true);
+ setSessionToken(mSession.getSessionToken());
+ notify = new com.muqingbfq.mq.NotificationManagerCompat(this);
+/* ;
+// 激活MediaSessionCompat
+ */
+ // 初始化通知栏
+ }
+
+ @Nullable
+ @Override
+ public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
+ return null;
+ }
+
+ @Override
+ public void onLoadChildren(@NonNull String parentId, @NonNull Result> result) {
+
+ }
+
+ public static void updateNotification() {
+ try {
+ // 更新通知栏的播放状态
+ if (notify.notificationBuilder != null) {
+ notify.tzl_an();
+ }
+ } catch (Exception e) {
+ gj.sc("bfqkz updateNotification:" + e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/fragment/bflb_db.java b/app/src/main/java/com/muqingbfq/fragment/bflb_db.java
new file mode 100644
index 0000000..1ccb6e7
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/bflb_db.java
@@ -0,0 +1,101 @@
+package com.muqingbfq.fragment;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.muqingbfq.R;
+import com.muqingbfq.api.url;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.list.MyViewHoder;
+import com.muqingbfq.main;
+import com.muqingbfq.xm;
+import com.muqingbfq.yc;
+
+public class bflb_db extends BottomSheetDialog {
+ public static String gdid;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fragment_bflb_db);
+ // 获取对话框窗口
+/* Window window = getWindow();
+ if (window != null) {
+ // 设置高度为默认值(例如:500dp)
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.height =
+ window.setAttributes(params);
+ }*/
+ // 设置默认弹出高度和最大上拉高度为 400dp
+ int height = main.g - main.g / 2 / 2;
+ getBehavior().setPeekHeight(height);
+ getBehavior().setMaxHeight(height);
+
+ try {
+ RecyclerView lb = findViewById(R.id.lb);
+ lb.setAdapter(new spq());
+ if (bfqkz.xm != null) {
+ lb.smoothScrollToPosition(bfqkz.list.indexOf(bfqkz.xm));
+ }
+ findViewById(R.id.xxbj).
+ setOnClickListener(v -> {
+ if (bfqkz.xm != null) {
+ lb.smoothScrollToPosition(bfqkz.list.indexOf(bfqkz.xm));
+ }
+ });
+ } catch (Exception e) {
+ yc.start(getContext(), e);
+ }
+ }
+
+ public bflb_db(Context context) {
+ super(context);
+ }
+
+ public static void start(Context context) {
+ new bflb_db(context).show();
+ }
+
+ class spq extends RecyclerView.Adapter {
+ @NonNull
+ @Override
+ public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_mp3, parent, false);
+ return new MyViewHoder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MyViewHoder holder, int position) {
+ xm x = bfqkz.list.get(position);
+ holder.name.setText(x.name);
+ holder.zz.setText(x.zz);
+ int color = ContextCompat.getColor(holder.getContext(), R.color.text);
+ if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) {
+ color = ContextCompat.getColor(holder.getContext(), R.color.text_cz);
+ }
+ holder.name.setTextColor(color);
+ holder.zz.setTextColor(color);
+ holder.view.setOnClickListener(view -> {
+ bfqkz.id = x.id;
+ if (bfqkz.xm != x) {
+ bfqkz.xm = x;
+ new url(x);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return bfqkz.list.size();
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/muqingbfq/fragment/bfq_db.java b/app/src/main/java/com/muqingbfq/fragment/bfq_db.java
new file mode 100644
index 0000000..58e051b
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/bfq_db.java
@@ -0,0 +1,57 @@
+package com.muqingbfq.fragment;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.muqingbfq.R;
+import com.muqingbfq.bfq;
+import com.muqingbfq.bfq_an;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.home;
+import com.muqingbfq.xm;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class bfq_db extends Fragment {
+ public View view;
+ public TextView name, zz;
+ public ImageView txa;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ view = inflater.inflate(R.layout.fragment_bfq_db, container, false);
+ name = view.findViewById(R.id.name);
+ zz = view.findViewById(R.id.zz);
+ txa = view.findViewById(R.id.kg);
+ txa.setOnClickListener(new bfq_an.kz());
+
+ view.findViewById(R.id.txb).setOnClickListener(view -> bflb_db.start(getContext()));
+ view.setOnClickListener(view12 -> bfq.start(home.appCompatActivity));
+
+// 恢复列表数据
+ SharedPreferences sharedPreferences = this.getContext().getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
+ String jsonList = sharedPreferences.getString("listData", ""); // 获取保存的 JSON 字符串
+ if (!jsonList.isEmpty()) {
+ Gson gson = new Gson();
+ Type type = new TypeToken>() {
+ }.getType();
+ bfqkz.list = gson.fromJson(jsonList, type); // 将 JSON 字符串转换回列表数据
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ return view;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/fragment/gd.java b/app/src/main/java/com/muqingbfq/fragment/gd.java
new file mode 100644
index 0000000..9a717a1
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/gd.java
@@ -0,0 +1,203 @@
+package com.muqingbfq.fragment;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.tabs.TabLayout;
+import com.muqingbfq.R;
+import com.muqingbfq.api.playlist;
+import com.muqingbfq.api.resource;
+import com.muqingbfq.bfq_an;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.list.list_gd;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.wj;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.muqingbfq.xm;
+
+public class gd extends Fragment {
+ public static String gdid;
+ public static BaseAdapter lbspq;
+ public static List list = new ArrayList<>();
+ public static JSONObject like = new JSONObject();
+ GridView gridView;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_gd, container, false);
+ lbspq = new baseadapter(view.getContext());
+ gridView = view.findViewById(R.id.wgbj);
+ gridView.setAdapter(lbspq);
+ if (gdid == null) {
+ gdid = main.mp3_csh;
+ }
+ TabLayout tabLayout = view.findViewById(R.id.tablayout);
+// tabLayout.removeAllTabs();
+ for (String name : new String[]{"推荐", "排行榜", "下载"}) {
+ TabLayout.Tab tab = tabLayout.newTab();
+ tab.setText(name);
+ tabLayout.addTab(tab);
+ }
+ new thread("推荐");
+ tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ list.clear();
+ lbspq.notifyDataSetChanged();
+ new thread(tab.getText().toString());
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+ }
+ });
+ try {
+ if (wj.cz(wj.mp3_like)) {
+ like = new JSONObject(wj.dqwb(wj.mp3_like));
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ return view;
+ }
+
+ class baseadapter extends BaseAdapter {
+ Context context;
+ LayoutInflater layoutInflater;
+
+ public baseadapter(Context context) {
+ this.context = context;
+ layoutInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public int getCount() {
+ return list.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return list.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @SuppressLint({"ResourceAsColor", "InflateParams", "ClickableViewAccessibility"})
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ ViewHoder viewHoder;
+ xm xm = list.get(i);
+ if (view == null) {
+ viewHoder = new ViewHoder();
+ view = layoutInflater.inflate(R.layout.list_gd, null, false);
+ viewHoder.textView = view.findViewById(R.id.wb1);
+ viewHoder.imageView = view.findViewById(R.id.fh);
+ viewHoder.cardView = view.findViewById(R.id.cardview);
+ viewHoder.kg = view.findViewById(R.id.kg);
+ view.setTag(viewHoder);
+ } else {
+ viewHoder = (ViewHoder) view.getTag();
+ }
+ list_gd gd = new list_gd(xm);
+ viewHoder.cardView.setOnClickListener(gd);
+ viewHoder.cardView.setOnLongClickListener(gd);
+ viewHoder.textView.setText(xm.name);
+ viewHoder.kg.setOnClickListener(view1 -> {
+ ImageView tx = (ImageView) view1;
+ new Thread() {
+ @Override
+ public void run() {
+ super.run();
+ boolean an=playlist.hq(bfqkz.list, xm.id);
+ main.handler.post(() -> {
+ if (an) {
+ bfq_an.xyq();
+ tx.setImageResource(R.drawable.bf);
+ main.edit.putString(main.mp3, xm.id);
+ main.edit.commit();
+ main.mp3_csh = gdid = xm.id;
+ }
+ com.muqingbfq.fragment.gd.lbspq.notifyDataSetChanged();
+ });
+ }
+ }.start();
+ });
+ int color = ContextCompat.getColor(context, R.color.text);
+ Drawable color_kg = ContextCompat.getDrawable(context, R.drawable.zt);
+ if (xm.id.equals(gdid)) {
+ color = ContextCompat.getColor(context, R.color.text_cz);
+ color_kg = ContextCompat.getDrawable(context, R.drawable.bf);
+ } else if (xm.cz) {
+ color = ContextCompat.getColor(context, R.color.text_cz_tm);
+ }
+ viewHoder.kg.setImageDrawable(color_kg);
+ viewHoder.textView.setTextColor(color);
+ Glide.with(context).load(xm.picurl).apply(new RequestOptions().placeholder(R.drawable.icon))
+ .into(viewHoder.imageView);
+// new wl(xm.picurl, Glide.with(context)).loadImage(bitmap -> viewHoder.imageView.setImageBitmap(bitmap));
+ return view;
+ }
+ }
+
+ class ViewHoder {
+ TextView textView;
+ ImageView imageView, kg;
+ MaterialCardView cardView;
+ }
+
+ class thread extends Thread {
+ String name;
+
+ public thread(String name) {
+ this.name = name;
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ switch (name) {
+ case "推荐":
+ resource.recommend(list);
+ break;
+ case "下载":
+ resource.下载(list);
+ break;
+ case "排行榜":
+ resource.排行榜(list);
+ break;
+ }
+ main.handler.post(() -> lbspq.notifyDataSetChanged());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/fragment/mp3.java b/app/src/main/java/com/muqingbfq/fragment/mp3.java
new file mode 100644
index 0000000..4504922
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/mp3.java
@@ -0,0 +1,141 @@
+package com.muqingbfq.fragment;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.muqingbfq.R;
+import com.muqingbfq.api.playlist;
+import com.muqingbfq.api.url;
+import com.muqingbfq.bfq;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.list.MyViewHoder;
+import com.muqingbfq.main;
+import com.muqingbfq.xm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class mp3 extends AppCompatActivity {
+ private final List list = new ArrayList<>();
+ public static RecyclerView.Adapter lbspq;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fragment_mp3);
+ Intent intent = getIntent();
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ toolbar.setTitle(intent.getStringExtra("name"));
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ lbspq = new spq();
+ RecyclerView lb = findViewById(R.id.lb);
+ LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+ lb.setLayoutManager(layoutManager);
+ lb.setAdapter(lbspq);
+ String id = intent.getStringExtra("id");
+ new start(id);
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ class start extends Thread {
+ String id;
+
+ public start(String id) {
+ this.id = id;
+ list.clear();
+ mp3.lbspq.notifyDataSetChanged();
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ if (id.equals("mp3_xz.json")) {
+ playlist.hq_xz(list);
+ } else if (id.equals("mp3_like.json")) {
+ playlist.hq_like(list);
+ } else {
+ playlist.hq(list, id);
+ }
+ main.handler.post(new lbspq_sx());
+ }
+ }
+
+ public static class lbspq_sx implements Runnable {
+ @SuppressLint("NotifyDataSetChanged")
+ @Override
+ public void run() {
+ lbspq.notifyDataSetChanged();
+ }
+ }
+
+ class spq extends RecyclerView.Adapter {
+
+ @NonNull
+ @Override
+ public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_mp3, parent, false);
+ return new MyViewHoder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MyViewHoder holder, int position) {
+ xm x = list.get(position);
+ holder.name.setText(x.name);
+ holder.zz.setText(x.zz);
+ int color = ContextCompat.getColor(holder.getContext(), R.color.text);
+ if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) {
+ color = ContextCompat.getColor(holder.getContext(), R.color.text_cz);
+ }
+ holder.name.setTextColor(color);
+ holder.zz.setTextColor(color);
+ holder.view.setOnClickListener(view -> {
+ bfqkz.id = x.id;
+ if (bfqkz.xm == null || !bfqkz.xm.id.equals(x.id)) {
+ bfqkz.xm = x;
+ new url(x);
+ }
+// if (!gd.gdid.equals(bflb_db.gdid)) {
+ bfqkz.list.clear();
+ int size = list.size();
+ for (int i = 0; i < size; i++) {
+ bfqkz.list.add(list.get(i));
+ }
+ bfq.start(mp3.this);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return list.size();
+ }
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public static void startactivity(Context context, String id) {
+ context.startActivity(new Intent(context, mp3.class).putExtra("id", id));
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/fragment/search.java b/app/src/main/java/com/muqingbfq/fragment/search.java
new file mode 100644
index 0000000..e6c5da0
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/search.java
@@ -0,0 +1,149 @@
+package com.muqingbfq.fragment;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.muqingbfq.R;
+import com.muqingbfq.activity_search;
+import com.muqingbfq.api.url;
+import com.muqingbfq.bfq;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.list.MyViewHoder;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.xm;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class search extends Fragment {
+
+ String name;
+
+ public search(String name) {
+ this.name = name;
+ }
+
+ View view;
+ RecyclerView.Adapter lbspq;
+ List list = new ArrayList<>();
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ view = inflater.inflate(R.layout.fragment_search, container, false);
+ TypedValue typedValue = new TypedValue();
+ requireContext().getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true);
+ // 设置背景颜色
+ view.setBackgroundColor(typedValue.data);
+ RecyclerView lb = view.findViewById(R.id.recyclerview);
+ LinearLayoutManager manager = new LinearLayoutManager(getContext());
+ lb.setLayoutManager(manager);
+ lbspq = new spq();
+ lb.setAdapter(lbspq);
+ new start().start();
+ return view;
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ public void setStart(String name) {
+ this.name = name;
+ list.clear();
+ lbspq.notifyDataSetChanged();
+ new start().start();
+
+ }
+
+ public class start extends Thread {
+ @SuppressLint("NotifyDataSetChanged")
+ @Override
+ public void run() {
+ super.run();
+ String hq = wl.hq("/search?keywords=" + name);
+ try {
+ JSONArray jsonArray = new JSONObject(hq).getJSONObject("result")
+ .getJSONArray("songs");
+ int length = jsonArray.length();
+ for (int i = 0; i < length; i++) {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ String id = jsonObject.getString("id");
+ String name = jsonObject.getString("name");
+ JSONArray artists = jsonObject.getJSONArray("artists");
+ int length1 = artists.length();
+ StringBuilder zz = null;
+ for (int j = 0; j < length1; j++) {
+ JSONObject josn = artists.getJSONObject(j);
+ String name_zz = josn.getString("name");
+ if (zz == null) {
+ zz = new StringBuilder(name_zz);
+ } else {
+ zz.append("/").append(name_zz);
+ }
+ }
+ list.add(new xm(id, name, zz.toString(), null));
+ }
+ main.handler.post(() -> lbspq.notifyDataSetChanged());
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+ }
+
+ class spq extends RecyclerView.Adapter {
+ @NonNull
+ @Override
+ public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_mp3, parent, false);
+ return new MyViewHoder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MyViewHoder holder, int position) {
+ xm x = list.get(position);
+ holder.name.setText(x.name);
+ holder.zz.setText(x.zz);
+ int color = ContextCompat.getColor(holder.getContext(), R.color.text);
+ if (bfqkz.xm != null && x.id.equals(bfqkz.xm.id)) {
+ color = ContextCompat.getColor(holder.getContext(), R.color.text_cz);
+ }
+ holder.name.setTextColor(color);
+ holder.zz.setTextColor(color);
+ holder.view.setOnClickListener(view1 -> {
+ bfqkz.id = x.id;
+ if (bfqkz.xm == null || !bfqkz.xm.id.equals(x.id)) {
+ bfqkz.xm = x;
+ new url(x);
+ }
+ if (!com.muqingbfq.fragment.gd.gdid.equals(bflb_db.gdid)) {
+ bfqkz.list.clear();
+ int size = list.size();
+ for (int i = 0; i < size; i++) {
+ bfqkz.list.add(list.get(i));
+ }
+ }
+ bfqkz.mt.start();
+ bfq.start(activity_search.appCompatActivity);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return list.size();
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/fragment/sz.java b/app/src/main/java/com/muqingbfq/fragment/sz.java
new file mode 100644
index 0000000..0f73eb2
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/fragment/sz.java
@@ -0,0 +1,74 @@
+package com.muqingbfq.fragment;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.muqingbfq.R;
+import com.muqingbfq.activity_about_software;
+import com.muqingbfq.login.user_logs;
+import com.muqingbfq.login.user_message;
+import com.muqingbfq.mq.gj;
+
+public class sz {
+
+ @SuppressLint("StaticFieldLeak")
+ public static TextView name, jieshao;
+ @SuppressLint("StaticFieldLeak")
+ public static ImageView imageView;
+ Context context;
+
+ public sz(Context context, View view) {
+ this.context = context;
+ name = view.findViewById(R.id.sz_text1);
+ jieshao = view.findViewById(R.id.sz_text2);
+ imageView = view.findViewById(R.id.image);
+ view.findViewById(R.id.xdbj).
+ setOnClickListener(v -> {
+ if (name.getText().equals("登录")) {
+ context.startActivity(new Intent(context, user_logs.class));
+ }
+ });
+ new user_message();
+ }
+
+ public static void switch_sz(Context context, int id) {
+ if (id == R.id.a) {
+ gj.llq(context, "https://rust.coldmint.top/ftp/muqing/");
+ } else if (id == R.id.b) {
+ context.startActivity(new Intent(context, com.muqingbfq.sz.class));
+// 设置中心
+ } else if (id == R.id.c) {
+// 储存清理
+ } else if (id == R.id.d) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("mqqapi://card/show_pslcard?card_type=group&uin="
+ + 674891685));
+ context.startActivity(intent);
+ } catch (Exception e) {
+ Toast.makeText(context, "无法打开 QQ", Toast.LENGTH_SHORT).show();
+ }
+ // 如果没有安装 QQ 客户端或无法打开 QQ,您可以在此处理异常
+// 官方聊群
+ } else if (id == R.id.e) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(
+ "mqqwpa://im/chat?chat_type=wpa&uin=" + "1966944300"));
+ context.startActivity(intent);
+ } catch (Exception e) {
+ // 如果没有安装 QQ 或无法跳转,则会抛出异常
+ Toast.makeText(context, "无法打开 QQ", Toast.LENGTH_SHORT).show();
+ }
+// 联系作者
+ } else if (id == R.id.f) {
+ context.startActivity(new Intent(context, activity_about_software.class));
+// 关于软件
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/home.java b/app/src/main/java/com/muqingbfq/home.java
new file mode 100644
index 0000000..d255f14
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/home.java
@@ -0,0 +1,178 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.LeadingMarginSpan;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.drawerlayout.widget.DrawerLayout;
+
+import com.google.android.material.navigation.NavigationView;
+import com.google.gson.Gson;
+import com.muqingbfq.fragment.bfq_db;
+import com.muqingbfq.mq.gj;
+
+public class home extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
+ @SuppressLint("StaticFieldLeak")
+ public static Toolbar toolbar;
+ public static AppCompatActivity appCompatActivity;
+
+ @SuppressLint("CommitTransaction")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_home);
+ appCompatActivity = this;
+ try {
+ toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ DrawerLayout drawerLayout = findViewById(R.id.chct);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, drawerLayout, toolbar, R.string.app_name, R.string.app_name);
+ drawerLayout.addDrawerListener(toggle);
+ toggle.syncState();
+ NavigationView chb = findViewById(R.id.chb);
+ chb.setNavigationItemSelectedListener(this);
+ Menu menu = chb.getMenu();
+ for (int i = 0; i < menu.size(); i++) {
+ MenuItem item = menu.getItem(i);
+ SpannableString spannableString = new SpannableString(item.getTitle());
+ spannableString.setSpan(new LeadingMarginSpan.Standard(
+ 26, 26), 0, spannableString.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ item.setTitle(spannableString);
+ }
+ new com.muqingbfq.fragment.sz(this, chb.getHeaderView(0));
+
+ db = new bfq_db();
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.bfq_db, db).commit();
+ mediaBrowser = new MediaBrowserCompat(this,
+ new ComponentName(this, bfqkz.class), connectionCallbacks, null);
+ mediaBrowser.connect();
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+
+ private MediaBrowserCompat mediaBrowser;
+ private final MediaBrowserCompat.ConnectionCallback connectionCallbacks =
+ new MediaBrowserCompat.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ // 连接成功后执行的操作
+ MediaControllerCompat mediaController;
+ try {
+ mediaController = new MediaControllerCompat(home.this,
+ mediaBrowser.getSessionToken());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ MediaControllerCompat.setMediaController(home.this, mediaController);
+
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ // 连接暂停时执行的操作
+// gj.ts(home.this,"zangting");
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ // 连接失败时执行的操作
+// gj.ts(home.this,"shibai");
+ }
+ };
+
+ @SuppressLint("StaticFieldLeak")
+ public static bfq_db db;
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // 保存列表数据
+ SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ Gson gson = new Gson();
+ String jsonList = gson.toJson(bfqkz.list); // 将列表数据转换为 JSON 字符串
+ editor.putString("listData", jsonList); // 保存 JSON 字符串到 SharedPreferences
+ editor.apply();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mediaBrowser.disconnect();
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // (see "stay in sync with the MediaSession")
+ if (MediaControllerCompat.getMediaController(home.this) != null) {
+// MediaControllerCompat.getMediaController(home.this).unregisterCallback(controllerCallback);
+ }
+ mediaBrowser.disconnect();
+ }
+
+ private long time;
+
+ @Override
+ public void onBackPressed() {
+ if (bfqkz.mt.isPlaying()) {
+ Intent home = new Intent(Intent.ACTION_MAIN);
+ home.addCategory(Intent.CATEGORY_HOME);
+ startActivity(home);
+ } else {
+ if (time < System.currentTimeMillis() - 1000) {
+ time = System.currentTimeMillis();
+ gj.ts(this, "再按一次退出软件");
+ } else {
+ finish();
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.home, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.menu_search) {
+ Intent intent = new Intent(this, activity_search.class);
+ startActivity(intent);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ com.muqingbfq.fragment.sz.switch_sz(this, item.getItemId());
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/list/MyViewHoder.java b/app/src/main/java/com/muqingbfq/list/MyViewHoder.java
new file mode 100644
index 0000000..be8a866
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/list/MyViewHoder.java
@@ -0,0 +1,26 @@
+package com.muqingbfq.list;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.muqingbfq.R;
+
+public class MyViewHoder extends RecyclerView.ViewHolder {
+ public TextView name, zz;
+ public View view;
+
+ public MyViewHoder(@NonNull View itemView) {
+ super(itemView);
+ view = itemView;
+ name = itemView.findViewById(R.id.wb1);
+ zz = itemView.findViewById(R.id.zz);
+ }
+
+ public Context getContext() {
+ return view.getContext();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/list/file_list.java b/app/src/main/java/com/muqingbfq/list/file_list.java
new file mode 100644
index 0000000..dcfd3e0
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/list/file_list.java
@@ -0,0 +1,5 @@
+package com.muqingbfq.list;
+
+public class file_list {
+
+}
diff --git a/app/src/main/java/com/muqingbfq/list/list_gd.java b/app/src/main/java/com/muqingbfq/list/list_gd.java
new file mode 100644
index 0000000..4130e4a
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/list/list_gd.java
@@ -0,0 +1,95 @@
+package com.muqingbfq.list;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.muqingbfq.R;
+import com.muqingbfq.api.playlist;
+import com.muqingbfq.fragment.gd;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.xm;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.util.Objects;
+
+public class list_gd implements View.OnClickListener, View.OnLongClickListener {
+ xm xm;
+
+ public list_gd(com.muqingbfq.xm xm) {
+ this.xm = xm;
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ @Override
+ public void onClick(View view) {
+// if (!gd.gdid.equals(xm.id)) {
+// gd.gdid = xm.id;
+ Context context = view.getContext();
+ Intent intent = new Intent(context, com.muqingbfq.fragment.mp3.class);
+ intent.putExtra("id", xm.id);
+ intent.putExtra("name", xm.name);
+ context.startActivity(intent);
+// mp3.startactivity(view.getContext(),xm.id);
+ }
+
+
+ @Override
+ public boolean onLongClick(View view) {
+ String[] stringArray = view.getResources()
+ .getStringArray(R.array.gd_list);
+ new MaterialAlertDialogBuilder(view.getContext()).setItems(stringArray, (dialog, id) -> {
+ new Thread() {
+ @Override
+ public void run() {
+ if (id == 0) {
+ String hq = wl.hq(playlist.api + xm.id + "&limit=30");
+ if (hq != null) {
+ wj.xrwb(wj.gd + xm.id, hq);
+ xm.cz = true;
+ try {
+ JSONObject jsonObject = new JSONObject();
+ if (wj.cz(wj.gd_xz)) {
+ jsonObject = new JSONObject(Objects.requireNonNull(wj.dqwb(wj.gd_xz)));
+ }
+ JSONObject json = new JSONObject();
+ json.put("name", xm.name);
+ json.put("picUrl", xm.picurl);
+ jsonObject.put(xm.id, json);
+ wj.xrwb(wj.gd_xz, jsonObject.toString());
+ } catch (JSONException e) {
+ gj.sc("list gd onclick thear " + e);
+ }
+ }
+
+ } else if (id == 2) {
+ wj.sc(wj.gd + xm.id);
+ if (xm.id.equals("mp3_xz.json")) {
+ wj.sc(new File(wj.mp3));
+ }
+ xm.cz = false;
+ try {
+ JSONObject jsonObject = new JSONObject(Objects.requireNonNull(wj.dqwb(wj.gd_xz)));
+ jsonObject.remove(xm.id);
+ wj.xrwb(wj.gd_xz, jsonObject.toString());
+ } catch (JSONException e) {
+ gj.sc(e);
+ }
+ }
+ main.handler.post(() -> gd.lbspq.notifyDataSetChanged());
+ }
+ }.start();
+ // 在这里处理菜单项的点击事件
+ dialog.dismiss();
+ }).show();
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/list/yylb.java b/app/src/main/java/com/muqingbfq/list/yylb.java
new file mode 100644
index 0000000..71c6f3b
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/list/yylb.java
@@ -0,0 +1,25 @@
+package com.muqingbfq.list;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.muqingbfq.R;
+import com.muqingbfq.api.url;
+import com.muqingbfq.bfq;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.fragment.bflb_db;
+import com.muqingbfq.fragment.mp3;
+import com.muqingbfq.home;
+import com.muqingbfq.xm;
+
+import java.util.List;
+
+public class yylb {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/login/enroll.java b/app/src/main/java/com/muqingbfq/login/enroll.java
new file mode 100644
index 0000000..4ef0982
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/login/enroll.java
@@ -0,0 +1,85 @@
+package com.muqingbfq.login;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.appcompat.app.AppCompatActivity;
+import com.muqingbfq.R;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.yc;
+
+import org.json.JSONObject;
+
+public class enroll extends AppCompatActivity {
+ EditText eduser,edpassword;
+ String user, password;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_enroll);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ Intent intent = getIntent();
+ eduser = findViewById(R.id.edit_user);
+ eduser.setText(
+ intent.getStringExtra("user"));
+ edpassword = findViewById(R.id.edit_password);
+ findViewById(R.id.edit_cookie).setOnClickListener(view -> new user_logs.erweima(view.getContext()));
+ findViewById(R.id.enroll).setOnClickListener(view -> a());
+ }
+
+ public void a() {
+ user = eduser.getText().toString();
+ password = edpassword.getText().toString();
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ View v = getWindow().peekDecorView();
+ if (null != v) {
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ new thread().start();
+ }
+
+ private void end() {
+ Intent intent = new Intent(); // 创建一个新意图
+ Bundle bundle = new Bundle(); // 创建一个新包裹
+ // 往包裹存入名叫response_time的字符串
+ bundle.putString("user", user);
+ // 往包裹存入名叫response_content的字符串
+ bundle.putString("password", password);
+ intent.putExtras(bundle); // 把快递包裹塞给意图
+
+ // 携带意图返回上一个页面。RESULT_OK表示处理成功
+ setResult(Activity.RESULT_OK, intent);
+
+ finish(); // 结束当前的活动页面
+ }
+
+ class thread extends Thread {
+ @Override
+ public void run() {
+ super.run();
+ String s = wl.get("http://139.196.224.229/muqing/enroll.php?user=" + user + "&password=" + password
+ + "&cookie" + wl.Cookie);
+ try {
+ JSONObject jsonObject = new JSONObject(s);
+ int code = jsonObject.getInt("code");
+ String msg = jsonObject.getString("msg");
+ main.handler.post(() -> gj.ts(enroll.this, msg));
+ if (code == 200) {
+ end();
+ }
+ } catch (Exception e) {
+ yc.start(e);
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/login/user_logs.java b/app/src/main/java/com/muqingbfq/login/user_logs.java
new file mode 100644
index 0000000..2cc53d7
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/login/user_logs.java
@@ -0,0 +1,222 @@
+package com.muqingbfq.login;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.util.Base64;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.muqingbfq.R;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wl;
+
+import org.json.JSONObject;
+
+import java.util.Objects;
+
+public class user_logs extends AppCompatActivity {
+
+ EditText edituser, editpassword;
+ Toolbar toolbar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_user_logs);
+ toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+ edituser = findViewById(R.id.edit_user);
+ editpassword = findViewById(R.id.edit_password);
+ findViewById(R.id.login).setOnClickListener(view -> new CloudUser(edituser.getText().toString()
+ , editpassword.getText().toString()));
+ findViewById(R.id.enroll).setOnClickListener(view -> {
+ Intent intent = new Intent(user_logs.this, enroll.class);
+ intent.putExtra("user", edituser.getText().toString());
+ startActivityForResult(intent, 0);
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (data != null && requestCode == 0 && resultCode == Activity.RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ String user = bundle.getString("user");
+ String password = bundle.getString("password");
+ edituser.setText(user);
+ editpassword.setText(password);
+ }
+ }
+ //some statement
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ public static Bitmap stringToBitmap(String string) {
+ Bitmap bitmap = null;
+ try {
+ byte[] bitmapArray = Base64.decode(string.split(",")[1], Base64.DEFAULT);
+ bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return bitmap;
+ }
+
+ class CloudUser extends Thread {
+ String user, password;
+
+ public CloudUser(String user, String password) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ View v = getWindow().peekDecorView();
+ if (null != v) {
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ this.user = user;
+ this.password = password;
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ String s = wl.get(main.http + "/user.php?" + "user=" + user + "&password=" + password);
+ try {
+ JSONObject jsonObject = new JSONObject(s);
+ int code = jsonObject.getInt("code");
+ String msg = jsonObject.getString("msg");
+ main.handler.post(() -> gj.ts(user_logs.this, msg));
+ if (code == 200) {
+ String cookie = jsonObject.getString("cookie");
+ if (wl.iskong()) {
+ new visitor();
+ }
+ wl.setcookie(cookie);
+ new user_message();
+ user_logs.this.finish();
+ }
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+ }
+
+ public static class erweima extends Thread {
+ int code = 800;
+ String unikey, qrimg, hq;
+ private long time=0;
+ ImageView imageView;
+ TextView textView;
+ MaterialAlertDialogBuilder materialAlertDialogBuilder;
+
+ public erweima(Context context) {
+ View inflate = LayoutInflater.from(context).inflate(R.layout.erweima, null);
+ imageView = inflate.findViewById(R.id.image);
+ textView = inflate.findViewById(R.id.text);
+// 创建布局参数对象
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(main.g, main.k);
+// 设置视图的布局参数
+ imageView.setLayoutParams(layoutParams);
+ materialAlertDialogBuilder = new MaterialAlertDialogBuilder(context) {
+ };
+ materialAlertDialogBuilder.setOnDismissListener(dialog -> {
+ // 对话框消失时触发的操作
+ // 可以在这里处理一些额外的逻辑
+ code = 0;
+ });
+ materialAlertDialogBuilder.setView(inflate).setTitle("请使用网易云音乐扫码");
+ materialAlertDialogBuilder.show();
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ while (code != 0) {
+ try {
+ hq = wl.hq("/login/qr/check?key=" + unikey + Time());
+ if (hq != null) {
+ JSONObject json = new JSONObject(hq);
+ code = json.getInt("code");
+ switch (code) {
+ case 800:
+ case 400:
+ setwb("二维码过期");
+ hqkey();
+ break;
+ case 801:
+ setwb("等待扫码");
+ break;
+ case 802:
+ setwb("等待确认");
+ break;
+ case 803:
+ setwb("登录成功");
+ wl.setcookie(json.getString("cookie"));
+ main.handler.postDelayed(() -> materialAlertDialogBuilder.create().cancel(),
+ 500);
+ code = 0;
+ break;
+ default:
+ code = 0;
+ // 默认情况下的操作
+ break;
+ }
+ }
+ sleep(1000);
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ }
+ }
+
+ private void hqkey() throws Exception {
+ unikey = new JSONObject(Objects.requireNonNull(wl.hq("/login/qr/key"))).
+ getJSONObject("data").getString("unikey");
+ JSONObject jsonObject = new JSONObject(Objects.requireNonNull(wl.hq("/login/qr/create?key=" +
+ unikey +
+ "&qrimg=base64")));
+ qrimg = jsonObject.getJSONObject("data").getString("qrimg");
+ main.handler.post(() -> imageView.setImageBitmap(stringToBitmap(qrimg)));
+ }
+
+ private String Time() {
+ if (time < System.currentTimeMillis() - 1000) {
+ time = System.currentTimeMillis();
+ }
+ return "×tamp" + time;
+ }
+
+ private void setwb(String wb) {
+ main.handler.post(() -> textView.setText(wb));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/login/user_message.java b/app/src/main/java/com/muqingbfq/login/user_message.java
new file mode 100644
index 0000000..090338b
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/login/user_message.java
@@ -0,0 +1,40 @@
+package com.muqingbfq.login;
+
+import com.bumptech.glide.Glide;
+import com.muqingbfq.R;
+import com.muqingbfq.fragment.sz;
+import com.muqingbfq.main;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.yc;
+
+import org.json.JSONObject;
+
+public class user_message extends Thread {
+
+ public user_message() {
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ String hq = wl.hq("/user/account" + "?cookie=" + wl.Cookie);
+ try {
+ JSONObject jsonObject = new JSONObject(hq).getJSONObject("profile");
+ String nickname = jsonObject.getString("nickname");
+ String signature = jsonObject.getString("signature");
+ String avatarUrl = jsonObject.getString("avatarUrl");
+ main.handler.post(() -> {
+ sz.name.setText(nickname);
+ sz.jieshao.setText(signature);
+ Glide.with(sz.imageView)
+ .load(avatarUrl)
+ .placeholder(R.drawable.icon)//图片加载出来前,显示的图片
+ .error(R.drawable.deleat)//图片加载失败后,显示的图片
+ .into(sz.imageView);
+ });
+ } catch (Exception e) {
+ yc.start(e);
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/login/visitor.java b/app/src/main/java/com/muqingbfq/login/visitor.java
new file mode 100644
index 0000000..9a4e392
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/login/visitor.java
@@ -0,0 +1,53 @@
+package com.muqingbfq.login;
+
+import android.content.Intent;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+import com.muqingbfq.yc;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class visitor extends Thread {
+
+ AppCompatActivity activity;
+ Intent intent;
+ public visitor(AppCompatActivity activity, Intent intent) {
+ this.activity = activity;
+ this.intent = intent;
+ start();
+ }
+
+ public visitor() {
+ start();
+ }
+ @Override
+ public void run() {
+ super.run();
+ String hq = wl.hq("/register/anonimous");
+ try {
+ JSONObject jsonObject = new JSONObject(hq);
+ wl.setcookie(jsonObject.getString("cookie"));
+ if (wj.filesdri == null) {
+ new wj(activity);
+ }
+ if (activity != null) {
+ activity.startActivity(intent);
+ activity.finish();
+ }
+ } catch (JSONException e) {
+ yc.start(activity, e);
+ }
+ try {
+ sleep(1000);
+ } catch (InterruptedException e) {
+ yc.start(activity,e);
+ }
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/main.java b/app/src/main/java/com/muqingbfq/main.java
new file mode 100644
index 0000000..3ab7311
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/main.java
@@ -0,0 +1,66 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.muqingbfq.mq.MyExceptionHandler;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+
+public class main extends Application {
+ @SuppressLint("StaticFieldLeak")
+ public static Context context;
+ public static Handler handler = new Handler(Looper.getMainLooper());
+ public static String api = "http://139.196.224.229:3000";
+ public static String http = "http://139.196.224.229/muqing";
+ public static int k, g;
+ public static SharedPreferences sp;
+ public static SharedPreferences.Editor edit;
+
+ public static String mp3 = "mp3", mp3_csh,
+ Time = "Time", Cookie = "Cookie";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ context = this;
+ new wj(this);
+ sp = getSharedPreferences("Set_up", MODE_PRIVATE);
+ edit = sp.edit();
+ if (sp.getLong(Time, 0) == 0) {
+ edit.putLong(Time, System.currentTimeMillis());
+ }
+ start.time = sp.getLong(Time, System.currentTimeMillis());
+ boolean bj = false;
+ try {
+ mp3_csh = sp.getString(mp3, "");
+ } catch (Exception e) {
+ edit.putString(mp3, "");
+ edit.commit();
+ mp3_csh = "";
+ }
+ try {
+ com.muqingbfq.bfqkz.ms = sp.getInt("ms", 1);
+ } catch (Exception e) {
+ edit.putInt("ms", 1);
+ bj = true;
+ com.muqingbfq.bfqkz.ms = 1;
+ }
+ try {
+ wl.Cookie = sp.getString(Cookie, "");
+ } catch (Exception e) {
+ edit.putString(Cookie, "");
+ wl.Cookie = "";
+ bj = true;
+ }
+ if (bj) {
+ edit.commit();
+ }
+ // 创建全局异常处理器实例 设置全局异常处理器
+ Thread.setDefaultUncaughtExceptionHandler(new MyExceptionHandler(this));
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java b/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java
new file mode 100644
index 0000000..1715726
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/BluetoothMusicController.java
@@ -0,0 +1,50 @@
+package com.muqingbfq.mq;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import com.muqingbfq.MyButtonClickReceiver;
+
+public class BluetoothMusicController {
+ private Context context;
+ private MediaSessionCompat mediaSession;
+
+ public BluetoothMusicController(Context context) {
+ this.context = context;
+// bluetoothReceiver.setOnHeadsetListener(this);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+
+ intentFilter.addAction("android.intent.action.HEADSET_PLUG");
+ context.registerReceiver(new MyButtonClickReceiver(), intentFilter);
+ registerHeadsetReceiver();
+// audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+// setupMediaSession();
+ }
+
+ public void stop() {
+ unregisterHeadsetReceiver();
+ }
+
+ // 注册媒体按钮事件接收器
+ @SuppressLint("ObsoleteSdkInt")
+ public void registerHeadsetReceiver() {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ ComponentName name = new ComponentName(context.getPackageName(), MyButtonClickReceiver.class.getName());
+ audioManager.registerMediaButtonEventReceiver(name);
+ }
+
+ // 注销媒体按钮事件接收器
+ public void unregisterHeadsetReceiver() {
+ if (mediaSession != null) {
+ mediaSession.setActive(false);
+ mediaSession.release();
+ mediaSession = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/MyExceptionHandler.java b/app/src/main/java/com/muqingbfq/mq/MyExceptionHandler.java
new file mode 100644
index 0000000..600fae7
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/MyExceptionHandler.java
@@ -0,0 +1,32 @@
+package com.muqingbfq.mq;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.muqingbfq.yc;
+
+public class MyExceptionHandler implements Thread.UncaughtExceptionHandler {
+ public static Throwable throwable;
+ private Context mContext;
+ public MyExceptionHandler(Context context) {
+ mContext = context;
+ }
+ @Override
+ public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
+ // 将异常信息打印到日志中
+ MyExceptionHandler.throwable = throwable;
+ String TAG = "MyExceptionHandler";
+ Log.e(TAG, "UncaughtException: ", throwable);
+ // 在这里执行生成错误报告的逻辑,例如将错误信息保存到文件或发送给服务器
+ // 可以使用第三方库,如ACRA、Bugsnag等,或者自行实现错误报告的处理逻辑
+ // 生成错误报告的逻辑
+ // 发送 Intent 重启应用
+ yc.start(mContext, throwable);
+ // 这里可以进行一些其他的操作,例如记录错误日志、弹出错误提示框等
+ // 终止程序
+// android.os.Process.killProcess(android.os.Process.myPid());
+// System.exit(0);
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java b/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java
new file mode 100644
index 0000000..21580b4
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/NotificationManagerCompat.java
@@ -0,0 +1,164 @@
+package com.muqingbfq.mq;
+
+import static com.muqingbfq.bfqkz.xm;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.media.session.MediaButtonReceiver;
+
+import com.muqingbfq.MyButtonClickReceiver;
+import com.muqingbfq.R;
+import com.muqingbfq.bfq;
+import com.muqingbfq.bfqkz;
+import com.muqingbfq.start;
+import com.muqingbfq.yc;
+
+public class NotificationManagerCompat {
+ Service context;
+ public NotificationCompat.Builder notificationBuilder;
+ public androidx.core.app.NotificationManagerCompat notificationManager;
+
+ public NotificationManagerCompat(Service context) {
+ this.context = context;
+ CharSequence name = context.getString(R.string.app_name);
+ String zz = context.getString(R.string.zz);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name,
+ NotificationManager.IMPORTANCE_LOW);
+ channel.setDescription(zz);
+ NotificationManager systemService = context.getSystemService(NotificationManager.class);
+ systemService.createNotificationChannel(channel);
+ if (!systemService.areNotificationsEnabled()) {
+ return;
+ }
+ }
+ // 适配12.0及以上
+ int flag;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ flag = PendingIntent.FLAG_IMMUTABLE;
+ } else {
+ flag = PendingIntent.FLAG_UPDATE_CURRENT;
+ }
+ // 设置启动的程序,如果存在则找出,否则新的启动
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(new ComponentName(context, start.class));//用ComponentName得到class对象
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);// 关键的一步,设置启动模式,两种情况
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, flag);
+ pendingIntent_kg = pendingIntent(context, new Intent(context, MyButtonClickReceiver.class).
+ setAction("kg"));
+ pendingIntent_syq = pendingIntent(context, new Intent(context, MyButtonClickReceiver.class).
+ setAction("syq"));
+ pendingIntent_xyq = pendingIntent(context, new Intent(context, MyButtonClickReceiver.class).
+ setAction("xyq"));
+ // 取消操作的PendingIntent
+// 取消操作的PendingIntent
+ PendingIntent cancelIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context,
+ PlaybackStateCompat.ACTION_STOP);
+ androidx.media.app.NotificationCompat.MediaStyle style =
+ new androidx.media.app.NotificationCompat.MediaStyle()
+ .setShowActionsInCompactView(0, 1, 2)
+ .setMediaSession(bfqkz.mSession.getSessionToken())
+ .setShowCancelButton(true)
+ .setCancelButtonIntent(cancelIntent);
+//
+ notificationBuilder = getNotificationBuilder(context)
+ .setSmallIcon(R.drawable.icon)
+ .setContentTitle(name).setContentText(zz)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setOngoing(true).setAutoCancel(false).setOnlyAlertOnce(true)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setContentIntent(pendingIntent)
+ .setStyle(style);
+ notificationManager = androidx.core.app.NotificationManagerCompat.from(context);
+ tzl_an();
+// context.startForeground(1, notificationBuilder.build());
+ } catch (Exception e) {
+ yc.start(context, e);
+ }
+ }
+
+ private PendingIntent pendingIntent_kg,
+ pendingIntent_syq,
+ pendingIntent_xyq;
+ private final String CHANNEL_ID = "muqing_yy_id";
+
+ @SuppressLint("RestrictedApi")
+ public void tzl_an() {
+ notificationBuilder.mActions.clear();
+ notificationBuilder
+/* .addAction(R.drawable.syq, "syq", pendingIntent_syq) // #0
+ .addAction(bfqkz.mt.isPlaying() ? R.drawable.bf : R.drawable.zt
+ , "kg", pendingIntent_kg) // #1
+ .addAction(R.drawable.xyq, "xyq", pendingIntent_xyq);*/
+ .addAction(android.R.drawable.ic_media_previous, "syq", pendingIntent_syq) // #0
+ .addAction(bfqkz.mt.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play
+ , "kg", pendingIntent_kg) // #1
+ .addAction(android.R.drawable.ic_media_next, "xyq", pendingIntent_xyq);
+ notificationBuilder.setOngoing(bfqkz.mt.isPlaying());
+ notificationManager_notify();
+ }
+
+ public void notificationManager_notify() {
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ notificationManager.notify(1, notificationBuilder.build());
+ }
+
+ @SuppressLint({"MissingPermission", "RestrictedApi"})
+ public void setBitmap(Bitmap bitmap) {
+ bfq.bitmap = bitmap;
+ if (bitmap == null) {
+ bitmap = BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.icon);
+ }
+ if (notificationManager != null) {
+ notificationBuilder.setContentTitle(xm.name);
+ notificationBuilder.setContentText(xm.zz);
+ notificationBuilder.setLargeIcon(bitmap);
+ notificationManager.notify(1, notificationBuilder.build());
+ }
+ if (bfq.tx != null) {
+ bfq.tx.setImageBitmap(bitmap);
+ }
+ }
+
+ private NotificationCompat.Builder getNotificationBuilder(Context context) {
+ // 适用于Android 8.0及以上版本
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return new NotificationCompat.Builder(context, CHANNEL_ID);
+ } else {
+ // Android 7.1及以下版本
+ return new NotificationCompat.Builder(context);
+ }
+ }
+
+ @SuppressLint("UnspecifiedImmutableFlag")
+ private PendingIntent pendingIntent(Context context, Intent intent) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ return PendingIntent.getBroadcast(context, 0, intent, 0);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/gj.java b/app/src/main/java/com/muqingbfq/mq/gj.java
new file mode 100644
index 0000000..851a4d9
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/gj.java
@@ -0,0 +1,94 @@
+package com.muqingbfq.mq;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.muqingbfq.main;
+import com.muqingbfq.yc;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+
+public class gj {
+ public static void ts(Context a, Object b) {
+ Toast.makeText(a, b.toString(), Toast.LENGTH_SHORT).show();
+ }
+
+ public static void xcts(Context context, Object b) {
+ main.handler.post(() -> Toast.makeText(context, b.toString(), Toast.LENGTH_SHORT).show());
+ }
+
+ public static void sc(Object a) {
+ if (a == null) {
+ a = "null";
+ }
+ Log.d("云音乐", String.valueOf(a));
+ }
+
+ public static void llq(Context context, String str) {
+ context.startActivity(new Intent(context, llq.class).putExtra("url", str));
+ }
+
+ public static void fx(Context context, String str) {
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, str);
+ context.startActivity(shareIntent);
+ }
+
+ public static int isDarkTheme(Context context) {
+ return context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+// return flag == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ public static boolean isWiFiConnected() {
+ try {
+ for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+ if (networkInterface.isUp() && !networkInterface.isLoopback()) {
+ if (networkInterface.getDisplayName().contains("wlan")) {
+ return true; // Wi-Fi网络
+ } else if (networkInterface.getDisplayName().contains("rmnet")) {
+ return false; // 流量网络
+ }
+ }
+ }
+ } catch (SocketException e) {
+ yc.start(main.context, e);
+ }
+ return false; // 默认为流量网络
+ }
+
+ public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int cornerRadius) {
+ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+
+ final int color = 0xff424242;
+ final Paint paint = new Paint();
+ final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ final RectF rectF = new RectF(rect);
+
+ paint.setAntiAlias(true);
+ canvas.drawARGB(0, 0, 0, 0);
+ paint.setColor(color);
+ canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(bitmap, rect, rect, paint);
+
+ return output;
+ }
+
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/llq.java b/app/src/main/java/com/muqingbfq/mq/llq.java
new file mode 100644
index 0000000..d0c025b
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/llq.java
@@ -0,0 +1,199 @@
+package com.muqingbfq.mq;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ProgressBar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.muqingbfq.R;
+
+import java.io.File;
+import java.util.Locale;
+
+public class llq extends AppCompatActivity {
+ WebView web;
+ Toolbar toolbar;
+ @SuppressLint({"DefaultLocale", "ObsoleteSdkInt"})
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_llq);
+
+ Intent intent = getIntent();
+
+ toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ web = findViewById(R.id.webview);
+ web.getSettings().setJavaScriptEnabled(true);
+ web.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ String title = view.getTitle();
+ toolbar.setTitle(title);
+ toolbar.setSubtitle(url);
+ // 在这里获取到了网页的标题
+ }
+ });
+ web.setDownloadListener((url1, userAgent, contentDisposition, mimetype, contentLength) -> {
+ String size = "0B";
+ if (contentLength > 0) {
+ final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
+ int digitGroups = (int) (Math.log10(contentLength) / Math.log10(1024));
+ size = String.format("%.1f %s", contentLength
+ / Math.pow(1024, digitGroups), units[digitGroups]);
+ }
+ final String filename = url1.substring(url1.lastIndexOf('/') + 1);
+ new MaterialAlertDialogBuilder(llq.this)
+ .setTitle(filename)
+ .setMessage("文件链接:" + url1 +
+ "\n文件大小:" + size)
+ .setNegativeButton("取消", null)
+ .setNegativeButton("下载", (dialogInterface, i) -> {
+ // 检查权限
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ ContextCompat.checkSelfPermission(llq.this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ // 如果没有写入存储的权限,则请求权限
+ ActivityCompat.requestPermissions(llq.this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ 1);
+ } else {
+ // 执行文件下载操作
+ DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url1));
+ // 设置下载保存路径和文件名
+ request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
+ // 允许使用的网络类型,手机、WIFI
+ request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
+ // 设置通知栏标题和描述信息
+ request.setTitle(filename);
+ request.setDescription("正在下载");
+ DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+ // 注册下载完成的广播接收器
+ long enqueue = downloadManager.enqueue(request);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (filename.endsWith(".apk")) {
+ // 打开安装界面
+ Cursor cursor = downloadManager.query(
+ new DownloadManager.Query().setFilterById(enqueue));
+ if (cursor.moveToFirst()) {
+ int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+ int status = cursor.getInt(columnIndex);
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
+ // 下载成功
+ columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
+ String localUri = cursor.getString(columnIndex);
+ if (localUri != null) {
+ Uri uri = Uri.parse(localUri);
+ String filePath = uri.getPath();
+ File file = new File(filePath);
+ // 获取下载文件的路径
+ Intent installIntent = new Intent(Intent.ACTION_VIEW);
+ installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
+ installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(installIntent);
+ }
+ }
+ }
+ }
+ }
+ };
+ registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ }
+ }).show();
+
+ });
+ final ProgressBar progressBar = findViewById(R.id.webViewProgressBar);
+ web.setWebChromeClient(new WebChromeClient() {
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ super.onProgressChanged(view, newProgress);
+ gj.sc(newProgress);
+ if (newProgress == 100) {
+ progressBar.setVisibility(View.GONE);
+ } else {
+ progressBar.setProgress(newProgress);
+ if (!progressBar.isShown()) {
+ progressBar.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ });
+ loadUrl(intent.getStringExtra("url"));
+ }
+
+ private void loadUrl(String url) {
+ web.loadUrl(url);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == 1 && grantResults.length > 0 &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // 权限已授予,执行文件下载操作
+ gj.ts(this, "权限已授予,请重新执行文件下载操作");
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.llq, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (web.canGoBack()) {
+ web.goBack();
+ } else {
+ finish();
+ }
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == android.R.id.home) {
+ finish();
+ } else if (itemId == R.id.fx) {
+ gj.fx(this, web.getUrl());
+// 服务中心
+ } else if (itemId == R.id.sx) {
+ web.reload();
+ } else if (itemId == R.id.menu_web) {
+ startActivity(new Intent(Intent.ACTION_VIEW,
+ Uri.parse(web.getUrl())));
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/wj.java b/app/src/main/java/com/muqingbfq/mq/wj.java
new file mode 100644
index 0000000..73c12e6
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/wj.java
@@ -0,0 +1,164 @@
+package com.muqingbfq.mq;
+
+import android.content.Context;
+
+import com.muqingbfq.yc;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class wj {
+ public static String filesdri;
+ public static String mp3 = "mp3/";
+ public static String lishi_json = "lishi.json";
+ public static String gd = "gd/";
+ public static String tx = "image/";
+ public static String gd_json = "gd.json", mp3_xz = "mp3_xz.json", gd_xz = "gd_xz.json",
+ gd_phb = "gd_phb.json", mp3_like = "like.json";
+
+ public wj(Context context) {
+ try {
+ wj.filesdri = context.getExternalFilesDir("").getAbsolutePath() + "/";
+// context.getFilesDir().toString() + "/";
+ gd_json = filesdri + gd_json;
+ mp3 = filesdri + mp3;
+ gd = filesdri + gd;
+ gd_xz = filesdri + gd_xz;
+ gd_phb = filesdri + gd_phb;
+ mp3_xz = gd + mp3_xz;
+ mp3_like = gd + mp3_like;
+ tx = filesdri + tx;
+ if (!new File(mp3).exists()) {
+ new File(mp3).mkdirs();
+ }
+ if (!new File(gd).exists()) {
+ new File(gd).mkdirs();
+ }
+ } catch (Exception e) {
+ yc.start(context, e);
+ }
+ }
+
+ public static boolean new_wj(String string) {
+ File file = new File(string);
+ if (file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ try {
+ return file.createNewFile();
+ } catch (IOException e) {
+ gj.sc(e);
+ }
+ return false;
+ }
+
+ /*
+ * 这里定义的是一个文件保存的方法,写入到文件中,所以是输出流
+ * */
+ public static boolean xrwb(String url, String text) {
+ File file = new File(url);
+//如果文件不存在,创建文件
+ try {
+ if (file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ if (!file.exists())
+ file.createNewFile();
+//创建FileOutputStream对象,写入内容
+ FileOutputStream fos = new FileOutputStream(file);
+//向文件中写入内容
+ fos.write(text.getBytes());
+ fos.close();
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ return false;
+ }
+
+ public static String dqwb(String url) {
+ try {
+ File file = new File(url);
+ FileInputStream fis = new FileInputStream(file);
+ BufferedReader br = new BufferedReader(new InputStreamReader(fis));
+ StringBuilder str = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ str.append(line);
+ }
+ br.close();
+ fis.close();
+ return str.toString();
+ } catch (Exception e) {
+ gj.sc(e);
+ }
+ return null;
+ }
+
+ public static boolean cz(String url) {
+ return new File(url).exists();
+ }
+
+ public static boolean sc(String url) {
+ File file = new File(url);
+ return file.delete();
+ }
+
+ public static void sc(File url) {
+ if (url.exists()) {
+ File[] files = url.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ // 递归调用,删除子文件夹及其内容
+ sc(file);
+ } else {
+ file.delete(); // 删除文件
+ }
+ }
+ }
+ url.delete(); // 删除当前文件夹
+ }
+ }
+
+
+ public String convertToMd5(String url) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] messageDigest = md.digest(url.getBytes());
+ StringBuilder hexString = new StringBuilder();
+ for (byte value : messageDigest) {
+ String hex = Integer.toHexString(0xFF & value);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static class xrwb extends Thread {
+ String url, text;
+
+ public xrwb(String url, String text) {
+ this.url = url;
+ this.text = text;
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ xrwb(url, text);
+ }
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/mq/wl.java b/app/src/main/java/com/muqingbfq/mq/wl.java
new file mode 100644
index 0000000..88a4cd4
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/mq/wl.java
@@ -0,0 +1,130 @@
+package com.muqingbfq.mq;
+
+
+import com.muqingbfq.main;
+import com.muqingbfq.xm;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+import okhttp3.Call;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+public class wl {
+ public static String Cookie;
+
+ public static void setcookie(String cookie) {
+ wl.Cookie = cookie;
+ main.edit.putString(main.Cookie, cookie);
+ main.edit.commit();
+ }
+
+ public static boolean iskong() {
+ return Cookie.equals("") || Cookie == null;
+ }
+ public static String hq(String url) {
+ try {
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(main.api + url)
+ .build();
+ Response response = client.newCall(request).execute();
+ if (response.body() != null) {
+ return response.body().string();
+ }
+ } catch (Exception e) {
+ gj.sc("wl hq(Strnig) " + e);
+ }
+ return null;
+ }
+
+ public static String get(String url) {
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(url)
+ .build();
+ try {
+ Response response = client.newCall(request).execute();
+ if (response.body() != null) {
+ return response.body().string();
+ }
+ } catch (Exception e) {
+ gj.sc("wl get(Strnig) " + e);
+ }
+ return null;
+ }
+
+ public static class xz extends Thread {
+ String url;
+ xm x;
+
+ public xz(String url, xm x) {
+ this.url = url;
+ this.x = x;
+ start();
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ xz(url, x);
+ }
+ }
+
+ public static boolean xz(String url, final xm x) {
+ try {
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ //访问路径
+ .url(url)
+ .build();
+ Call call = client.newCall(request);
+ Response response = call.execute();
+ if (response.isSuccessful()) {
+ ResponseBody body = response.body();
+ if (body != null) {
+ File file = new File(wj.mp3, String.valueOf(x.id));
+ InputStream inputStream = body.byteStream();
+ FileOutputStream fileOutputStream =
+ new FileOutputStream(file);
+ // 替换为实际要保存的文件路径
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ fileOutputStream.write(buffer, 0, bytesRead);
+ }
+ fileOutputStream.close();
+ inputStream.close();
+ }
+ }
+ JSONObject jsonObject = new JSONObject();
+ if (wj.cz(wj.mp3_xz)) {
+ jsonObject = new JSONObject(wj.dqwb(wj.mp3_xz));
+ } else {
+ jsonObject.put("songs", new JSONArray());
+ }
+ JSONArray songs = jsonObject.getJSONArray("songs");
+ if (songs.length() > 30) {
+ songs.remove(0);
+ }
+ JSONObject json = new JSONObject();
+ json.put("id", x.id);
+ json.put("name", x.name);
+ json.put("zz", x.zz);
+ json.put("picUrl", x.picurl);
+ songs.put(json);
+ wj.xrwb(wj.mp3_xz, jsonObject.toString());
+ return true;
+ } catch (Exception e) {
+ gj.sc("wl xz " + e);
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/muqingbfq/start.java b/app/src/main/java/com/muqingbfq/start.java
new file mode 100644
index 0000000..3c1e280
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/start.java
@@ -0,0 +1,172 @@
+package com.muqingbfq;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.muqingbfq.login.visitor;
+import com.muqingbfq.mq.gj;
+import com.muqingbfq.mq.wj;
+import com.muqingbfq.mq.wl;
+
+public class start extends AppCompatActivity {
+ public static int ztl, dhl;
+ Intent home;
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ home = new Intent(this, home.class);
+ ztl = getNavigationBarHeight(this);
+ dhl = getStatusBarHeight(this);
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ main.k = dm.widthPixels;
+ main.g = dm.heightPixels;
+ } catch (Exception e) {
+ yc.start(this, e);
+ }
+ if (Build.VERSION.SDK_INT >= 33) {
+ int checkPermission =
+ ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS);
+ if (checkPermission != PackageManager.PERMISSION_GRANTED) {
+ //动态申请
+ ActivityCompat.requestPermissions(this, new String[]{
+ Manifest.permission.POST_NOTIFICATIONS}, REQUEST_EXTERNAL_STORAGE);
+ } else {
+ startApp();
+ }
+ } else {
+ startApp();
+ }
+// checkPermission();
+ }
+
+ public static long time;
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ }
+
+ public static int getStatusBarHeight(Context context) {
+ int result = 0;
+ @SuppressLint({"InternalInsetResource", "DiscouragedApi"}) int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ result = context.getResources().getDimensionPixelSize(resourceId);
+ }
+ return result;
+ }
+
+ private int getNavigationBarHeight(Context context) {
+ Resources resources = context.getResources();
+ @SuppressLint({"InternalInsetResource", "DiscouragedApi"}) int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ private static final int REQUEST_EXTERNAL_STORAGE = 1;
+ private static final String[] PERMISSIONS_STORAGE = {android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE};
+
+ private AlertDialog dialog;
+
+ @SuppressLint("ObsoleteSdkInt")
+ private void checkPermission() {
+ //检查权限(NEED_PERMISSION)是否被授权 PackageManager.PERMISSION_GRANTED表示同意授权
+ if (Build.VERSION.SDK_INT >= 30) {
+ if (!Environment.isExternalStorageManager()) {
+ if (dialog != null) {
+ dialog.dismiss();
+ dialog = null;
+ }
+ dialog = new AlertDialog.Builder(this).setTitle("提示")//设置标题
+ .setMessage("请开启文件访问权限,否则无法正常使用本应用!").setNegativeButton("取消", (dialog, i) -> dialog.dismiss()).setPositiveButton("确定", (dialog, which) -> {
+ dialog.dismiss();
+ Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
+ startActivity(intent);
+ }).create();
+ dialog.show();
+ } else {
+ gj.sc("Android 11以上,当前已有权限");
+ startApp();
+ }
+ } else {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ //申请权限
+ if (dialog != null) {
+ dialog.dismiss();
+ dialog = null;
+ }
+ dialog = new AlertDialog.Builder(this).setTitle("提示")//设置标题
+ .setMessage("请开启文件访问权限,否则无法正常使用本应用!").setPositiveButton("确定", (dialog, which) -> {
+ dialog.dismiss();
+ ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
+ }).create();
+ dialog.show();
+ } else {
+ gj.sc("Android 6.0以上,11以下,当前已有权限");
+ startApp();
+ }
+ } else {
+ gj.sc("Android 6.0以下,已获取权限");
+ startApp();
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_EXTERNAL_STORAGE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "授权成功!", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "授权被拒绝!", Toast.LENGTH_SHORT).show();
+ }
+ }
+ startApp();
+ }
+
+ private void startApp() {
+
+ SharedPreferences theme = getSharedPreferences("theme", MODE_PRIVATE);
+ @SuppressLint("CommitPrefEdits") SharedPreferences.Editor edit = theme.edit();
+ int i = theme.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+ if (i == -1) {
+ edit.putInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+ }
+ AppCompatDelegate.setDefaultNightMode(i);
+
+ wl.Cookie = main.sp.getString(main.Cookie, "");
+ if (wl.Cookie.equals("")) {
+ new visitor(this, home);
+ } else {
+ if (wj.filesdri == null) {
+ new wj(this);
+ }
+ startActivity(home);
+ finish();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/sz.java b/app/src/main/java/com/muqingbfq/sz.java
new file mode 100644
index 0000000..5043912
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/sz.java
@@ -0,0 +1,73 @@
+package com.muqingbfq;
+
+import android.annotation.SuppressLint;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.widget.Toolbar;
+
+import com.google.android.material.materialswitch.MaterialSwitch;
+
+public class sz extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_sz);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ UI();
+ }
+
+ @SuppressLint("ApplySharedPref")
+ private void UI() {
+ MaterialSwitch a1 = findViewById(R.id.switch_a1);
+ MaterialSwitch a2 = findViewById(R.id.switch_a2);
+ SharedPreferences theme = getSharedPreferences("theme", MODE_PRIVATE);
+ @SuppressLint("CommitPrefEdits") SharedPreferences.Editor edit = theme.edit();
+ int i = theme.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+ if (i == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
+ a1.setChecked(true);
+ a2.setEnabled(false);
+ } else {
+ a1.setChecked(false);
+ a2.setEnabled(true);
+ a2.setChecked(i == AppCompatDelegate.MODE_NIGHT_YES);
+ }
+ a1.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (b) {
+// 跟随系统设置切换颜色模式
+ int ms = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
+ AppCompatDelegate.setDefaultNightMode(ms);
+ edit.putInt("theme", ms);
+ edit.commit();
+ }
+ a2.setEnabled(!b);
+ });
+ a2.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (compoundButton.isEnabled()) {
+ int ms;
+ if (b) {
+ ms = AppCompatDelegate.MODE_NIGHT_YES;
+ } else {
+ ms = AppCompatDelegate.MODE_NIGHT_NO;
+ }
+ AppCompatDelegate.setDefaultNightMode(ms);
+ edit.putInt("theme", ms);
+ edit.commit();
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/xm.java b/app/src/main/java/com/muqingbfq/xm.java
new file mode 100644
index 0000000..e8b8f8a
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/xm.java
@@ -0,0 +1,25 @@
+package com.muqingbfq;
+
+public class xm {
+ public String id, name, zz;
+ public Object picurl;
+ public boolean cz;
+ public xm(String id, String name, String zz, String picurl) {
+ this.id = id;
+ this.name = name;
+ this.zz = zz;
+ this.picurl = picurl;
+ }
+ public xm(String id, String name, String picurl, boolean cz) {
+ this.id = id;
+ this.name = name;
+ this.picurl = picurl;
+ this.cz = cz;
+ }
+ public xm(String id, String name, int picurl, boolean cz) {
+ this.id = id;
+ this.name = name;
+ this.picurl = picurl;
+ this.cz = cz;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/muqingbfq/yc.java b/app/src/main/java/com/muqingbfq/yc.java
new file mode 100644
index 0000000..8038a75
--- /dev/null
+++ b/app/src/main/java/com/muqingbfq/yc.java
@@ -0,0 +1,34 @@
+package com.muqingbfq;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class yc extends AppCompatActivity {
+ public static Object exception;
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_yc);
+ TextView text = findViewById(R.id.text);
+ try {
+ text.setText(exception.toString());
+ } catch (Exception e) {
+ Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public static void start(Object e) {
+ start(main.context, e);
+ }
+
+ public static void start(Context context, Object e) {
+ yc.exception = e;
+ context.startActivity(new Intent(context,yc.class));
+ }
+}
diff --git a/app/src/main/logo-playstore.png b/app/src/main/logo-playstore.png
new file mode 100644
index 0000000..58ac109
Binary files /dev/null and b/app/src/main/logo-playstore.png differ
diff --git a/app/src/main/res/anim/slide_in.xml b/app/src/main/res/anim/slide_in.xml
new file mode 100644
index 0000000..ce27a16
--- /dev/null
+++ b/app/src/main/res/anim/slide_in.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/app/src/main/res/anim/slide_out.xml b/app/src/main/res/anim/slide_out.xml
new file mode 100644
index 0000000..b98eddd
--- /dev/null
+++ b/app/src/main/res/anim/slide_out.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml b/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml
new file mode 100644
index 0000000..eeaf9ea
--- /dev/null
+++ b/app/src/main/res/drawable/abc_ic_menu_share_mtrl_alpha.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/app_warning.xml b/app/src/main/res/drawable/app_warning.xml
new file mode 100644
index 0000000..35b9685
--- /dev/null
+++ b/app/src/main/res/drawable/app_warning.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml
new file mode 100644
index 0000000..0ea92cf
--- /dev/null
+++ b/app/src/main/res/drawable/background.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/bf.xml b/app/src/main/res/drawable/bf.xml
new file mode 100644
index 0000000..53eafb3
--- /dev/null
+++ b/app/src/main/res/drawable/bf.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bw.xml b/app/src/main/res/drawable/bw.xml
new file mode 100644
index 0000000..3bfef8c
--- /dev/null
+++ b/app/src/main/res/drawable/bw.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+
+
+
diff --git a/app/src/main/res/drawable/cd.xml b/app/src/main/res/drawable/cd.xml
new file mode 100644
index 0000000..b6d321b
--- /dev/null
+++ b/app/src/main/res/drawable/cd.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/deleat.xml b/app/src/main/res/drawable/deleat.xml
new file mode 100644
index 0000000..abe2d15
--- /dev/null
+++ b/app/src/main/res/drawable/deleat.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/end.xml b/app/src/main/res/drawable/end.xml
new file mode 100644
index 0000000..5b68ef1
--- /dev/null
+++ b/app/src/main/res/drawable/end.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/fuwuzhongxing.xml b/app/src/main/res/drawable/fuwuzhongxing.xml
new file mode 100644
index 0000000..d547cd6
--- /dev/null
+++ b/app/src/main/res/drawable/fuwuzhongxing.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..a22bf42
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..a5b12f6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/icon.xml b/app/src/main/res/drawable/icon.xml
new file mode 100644
index 0000000..f51187c
--- /dev/null
+++ b/app/src/main/res/drawable/icon.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/liaoqun.xml b/app/src/main/res/drawable/liaoqun.xml
new file mode 100644
index 0000000..dc489db
--- /dev/null
+++ b/app/src/main/res/drawable/liaoqun.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/logo.xml b/app/src/main/res/drawable/logo.xml
new file mode 100644
index 0000000..b003cdd
--- /dev/null
+++ b/app/src/main/res/drawable/logo.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/mt_sj.xml b/app/src/main/res/drawable/mt_sj.xml
new file mode 100644
index 0000000..045192a
--- /dev/null
+++ b/app/src/main/res/drawable/mt_sj.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/mt_sx.xml b/app/src/main/res/drawable/mt_sx.xml
new file mode 100644
index 0000000..3271662
--- /dev/null
+++ b/app/src/main/res/drawable/mt_sx.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/mt_xh.xml b/app/src/main/res/drawable/mt_xh.xml
new file mode 100644
index 0000000..bc1ea78
--- /dev/null
+++ b/app/src/main/res/drawable/mt_xh.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/progress_bar_horizontal.xml b/app/src/main/res/drawable/progress_bar_horizontal.xml
new file mode 100644
index 0000000..39217ff
--- /dev/null
+++ b/app/src/main/res/drawable/progress_bar_horizontal.xml
@@ -0,0 +1,11 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/qq.xml b/app/src/main/res/drawable/qq.xml
new file mode 100644
index 0000000..b7fda57
--- /dev/null
+++ b/app/src/main/res/drawable/qq.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/set_up.xml b/app/src/main/res/drawable/set_up.xml
new file mode 100644
index 0000000..88a178d
--- /dev/null
+++ b/app/src/main/res/drawable/set_up.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_sheet_dialog_bg.xml b/app/src/main/res/drawable/shape_sheet_dialog_bg.xml
new file mode 100644
index 0000000..6eab874
--- /dev/null
+++ b/app/src/main/res/drawable/shape_sheet_dialog_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/sousuo.xml b/app/src/main/res/drawable/sousuo.xml
new file mode 100644
index 0000000..40c0dc2
--- /dev/null
+++ b/app/src/main/res/drawable/sousuo.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/start.xml b/app/src/main/res/drawable/start.xml
new file mode 100644
index 0000000..f313809
--- /dev/null
+++ b/app/src/main/res/drawable/start.xml
@@ -0,0 +1,9 @@
+
+
+
+ -
+
+
+
diff --git a/app/src/main/res/drawable/syq.xml b/app/src/main/res/drawable/syq.xml
new file mode 100644
index 0000000..740e3e1
--- /dev/null
+++ b/app/src/main/res/drawable/syq.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ui_editview.xml b/app/src/main/res/drawable/ui_editview.xml
new file mode 100644
index 0000000..d0fde8d
--- /dev/null
+++ b/app/src/main/res/drawable/ui_editview.xml
@@ -0,0 +1,9 @@
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/user_end.xml b/app/src/main/res/drawable/user_end.xml
new file mode 100644
index 0000000..01c74c5
--- /dev/null
+++ b/app/src/main/res/drawable/user_end.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/xyq.xml b/app/src/main/res/drawable/xyq.xml
new file mode 100644
index 0000000..810a10a
--- /dev/null
+++ b/app/src/main/res/drawable/xyq.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/zt.xml b/app/src/main/res/drawable/zt.xml
new file mode 100644
index 0000000..d0e9098
--- /dev/null
+++ b/app/src/main/res/drawable/zt.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_about_software.xml b/app/src/main/res/layout/activity_about_software.xml
new file mode 100644
index 0000000..80fca98
--- /dev/null
+++ b/app/src/main/res/layout/activity_about_software.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_enroll.xml b/app/src/main/res/layout/activity_enroll.xml
new file mode 100644
index 0000000..1e9c452
--- /dev/null
+++ b/app/src/main/res/layout/activity_enroll.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ =
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..81daddf
--- /dev/null
+++ b/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_llq.xml b/app/src/main/res/layout/activity_llq.xml
new file mode 100644
index 0000000..942e755
--- /dev/null
+++ b/app/src/main/res/layout/activity_llq.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml
new file mode 100644
index 0000000..f78917a
--- /dev/null
+++ b/app/src/main/res/layout/activity_search.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_sz.xml b/app/src/main/res/layout/activity_sz.xml
new file mode 100644
index 0000000..e2f6897
--- /dev/null
+++ b/app/src/main/res/layout/activity_sz.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_user_logs.xml b/app/src/main/res/layout/activity_user_logs.xml
new file mode 100644
index 0000000..d2132db
--- /dev/null
+++ b/app/src/main/res/layout/activity_user_logs.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_yc.xml b/app/src/main/res/layout/activity_yc.xml
new file mode 100644
index 0000000..5c18125
--- /dev/null
+++ b/app/src/main/res/layout/activity_yc.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/app/src/main/res/layout/erweima.xml b/app/src/main/res/layout/erweima.xml
new file mode 100644
index 0000000..3598544
--- /dev/null
+++ b/app/src/main/res/layout/erweima.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_bflb_db.xml b/app/src/main/res/layout/fragment_bflb_db.xml
new file mode 100644
index 0000000..391842a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_bflb_db.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_bfq.xml b/app/src/main/res/layout/fragment_bfq.xml
new file mode 100644
index 0000000..4bcc7a8
--- /dev/null
+++ b/app/src/main/res/layout/fragment_bfq.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_bfq_db.xml b/app/src/main/res/layout/fragment_bfq_db.xml
new file mode 100644
index 0000000..8144c0f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_bfq_db.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_gd.xml b/app/src/main/res/layout/fragment_gd.xml
new file mode 100644
index 0000000..7f812f2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_gd.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_mp3.xml b/app/src/main/res/layout/fragment_mp3.xml
new file mode 100644
index 0000000..3356050
--- /dev/null
+++ b/app/src/main/res/layout/fragment_mp3.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
new file mode 100644
index 0000000..f97f67a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_sz.xml b/app/src/main/res/layout/fragment_sz.xml
new file mode 100644
index 0000000..08bb813
--- /dev/null
+++ b/app/src/main/res/layout/fragment_sz.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_gd.xml b/app/src/main/res/layout/list_gd.xml
new file mode 100644
index 0000000..18fc98e
--- /dev/null
+++ b/app/src/main/res/layout/list_gd.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_mp3.xml b/app/src/main/res/layout/list_mp3.xml
new file mode 100644
index 0000000..6773e6e
--- /dev/null
+++ b/app/src/main/res/layout/list_mp3.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_text.xml b/app/src/main/res/layout/list_text.xml
new file mode 100644
index 0000000..bf785c0
--- /dev/null
+++ b/app/src/main/res/layout/list_text.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/app/src/main/res/layout/notification_layout.xml b/app/src/main/res/layout/notification_layout.xml
new file mode 100644
index 0000000..da1ef19
--- /dev/null
+++ b/app/src/main/res/layout/notification_layout.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/bfq.xml b/app/src/main/res/menu/bfq.xml
new file mode 100644
index 0000000..6228960
--- /dev/null
+++ b/app/src/main/res/menu/bfq.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/gd.xml b/app/src/main/res/menu/gd.xml
new file mode 100644
index 0000000..b97db27
--- /dev/null
+++ b/app/src/main/res/menu/gd.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/home.xml b/app/src/main/res/menu/home.xml
new file mode 100644
index 0000000..c3c01f6
--- /dev/null
+++ b/app/src/main/res/menu/home.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/llq.xml b/app/src/main/res/menu/llq.xml
new file mode 100644
index 0000000..8d67e4d
--- /dev/null
+++ b/app/src/main/res/menu/llq.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/search.xml b/app/src/main/res/menu/search.xml
new file mode 100644
index 0000000..ea85875
--- /dev/null
+++ b/app/src/main/res/menu/search.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/sz.xml b/app/src/main/res/menu/sz.xml
new file mode 100644
index 0000000..14cc4c5
--- /dev/null
+++ b/app/src/main/res/menu/sz.xml
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..a2aab2e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..34a3d90
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/like.png b/app/src/main/res/mipmap-hdpi/like.png
new file mode 100644
index 0000000..ececda2
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/like.png differ
diff --git a/app/src/main/res/mipmap-hdpi/logo.png b/app/src/main/res/mipmap-hdpi/logo.png
new file mode 100644
index 0000000..ce14889
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/logo.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..cbbdf55
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ed592f6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..4c7182c
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b4eacd6
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..38d1d37
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..3bbb7ce
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..f3dcbfb
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ff4f081
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..41f35ef
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,6 @@
+
+
+ @color/white
+ #99FFFFFF
+ #323232
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..6f8164a
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..bc776ad
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b54284a
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+ #0088FF
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
+ #03A9F4
+ #9903A9F4
+ @color/black
+ #80000000
+ #00FFFFFF
+ @color/white
+
+ #A2A2A2
+ #000000
+ #343434
+ #809E9E9E
+ #809E9E9E
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..43af05e
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,14 @@
+
+
+ 16dp
+
+ 1000
+ 16sp
+ 12sp
+
+ 24dp
+ 5dp
+ 1dp
+ 30dp
+ 40dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0f1b3dc
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,45 @@
+
+ 云音乐
+ 设置
+ 软件名称:云音乐\n开发者:MUQING\n平台:Android/HarmonyOS\n软件介绍:\n
+云音乐是一款对接了网易云SDK的音乐媒体娱乐软件。它提供了丰富的音乐资源和功能,让用户可以随时随地畅享高品质的音乐体验。\n
+主要特点:\n
+丰富的音乐库:云音乐对接了网易云SDK,涵盖了各种流派和风格的歌曲,让用户可以根据自己的喜好随心选择音乐库。\n
+用户个性化推荐:基于智能算法,云音乐为用户提供个性化的音乐推荐,根据用户的听歌历史、偏好和趋向,推荐适合用户口味的音乐。\n
+歌曲搜索和在线播放:用户可以通过关键词搜索自己喜欢的歌曲,也可以在线播放音乐,无需下载。\n
+歌曲收藏和创建歌单:用户可以将喜欢的歌曲收藏起来,创建自己的歌单,方便随时收听。\n
+超清音质:云音乐支持高品质的音乐播放,让用户感受更加真实、清晰的音乐体验。\n
+歌词显示和歌曲分享:云音乐还提供歌词显示功能,让用户可以跟随歌词唱歌。同时,用户也可以将自己喜欢的歌曲分享给朋友。\n
+无论你是需要放松身心、工作休闲,或者在旅途中享受音乐,云音乐都能满足你的需求。欢迎下载使用云音乐,让音乐成为你生活的一部分。\n
+\n
+请注意,以上只是一个软件介绍的示例,软件具体开发工程还没有完善,具体的介绍内容可以根据实际情况进行体验软件。\n
+ 作者
+ 名称
+ 播放列表
+ 登录
+ channel_id
+ 分享
+ 滑动窗体
+ Cookie
+ 退出登录
+ 搜索
+ 关于软件
+ 捐赠软件
+ 服务中心
+ 设置中心
+ 储存清理
+ 官方聊群
+ 联系作者
+ 关于软件
+ 登录
+ 删除
+ 历史记录
+ 00:00
+ 音乐
+ 歌单
+
+ - 下载歌单
+ - 收藏歌单(不可用)
+ - 删除歌单
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..7a8b3ed
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..f1a88cd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '8.1.0' apply false
+ id 'com.android.library' version '7.1.3' apply false
+ id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..3e927b1
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b49c0ae
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Oct 21 17:18:56 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..48110c8
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
+ }
+}
+
+rootProject.name = "Cloud_music"
+include ':app'