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 @@ + + + + + + + + + + + + + + + + +