mirror of
https://github.com/fankes/ProjectPromote.git
synced 2025-09-04 01:35:32 +08:00
Initial commit
This commit is contained in:
1
projectpromote/.gitignore
vendored
Normal file
1
projectpromote/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
87
projectpromote/build.gradle.kts
Normal file
87
projectpromote/build.gradle.kts
Normal file
@@ -0,0 +1,87 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.android.library)
|
||||
autowire(libs.plugins.kotlin.android)
|
||||
autowire(libs.plugins.maven.publish)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = property.project.projectpromote.groupName
|
||||
compileSdk = property.project.android.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = property.project.android.minSdk
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(io.noties.markwon.core)
|
||||
implementation(io.noties.markwon.image)
|
||||
implementation(io.noties.markwon.html)
|
||||
implementation(com.squareup.okhttp3.okhttp)
|
||||
implementation(androidx.lifecycle.lifecycle.runtime.ktx)
|
||||
implementation(androidx.core.core.ktx)
|
||||
implementation(androidx.appcompat.appcompat)
|
||||
implementation(com.google.android.material.material)
|
||||
testImplementation(junit.junit)
|
||||
androidTestImplementation(androidx.test.ext.junit)
|
||||
androidTestImplementation(androidx.test.espresso.espresso.core)
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
val repositoryDir = gradle.gradleUserHomeDir
|
||||
.resolve("fankes-maven-repository")
|
||||
.resolve("repository")
|
||||
maven {
|
||||
name = "FankesMavenReleases"
|
||||
url = repositoryDir.resolve("releases").toURI()
|
||||
}
|
||||
maven {
|
||||
name = "FankesMavenSnapShots"
|
||||
url = repositoryDir.resolve("snapshots").toURI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
coordinates(property.project.projectpromote.groupName, property.project.projectpromote.moduleName, property.project.projectpromote.version)
|
||||
pom {
|
||||
name = property.project.name
|
||||
description = property.project.description
|
||||
url = property.project.url
|
||||
licenses {
|
||||
license {
|
||||
name = property.project.licence.name
|
||||
url = property.project.licence.url
|
||||
distribution = property.project.licence.url
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = property.project.developer.id
|
||||
name = property.project.developer.name
|
||||
email = property.project.developer.email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
projectpromote/consumer-rules.pro
Normal file
0
projectpromote/consumer-rules.pro
Normal file
21
projectpromote/proguard-rules.pro
vendored
Normal file
21
projectpromote/proguard-rules.pro
vendored
Normal file
@@ -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
|
@@ -0,0 +1,25 @@
|
||||
package com.fankes.projectpromote
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.fankes.projectpromote.test", appContext.packageName)
|
||||
}
|
||||
}
|
5
projectpromote/src/main/AndroidManifest.xml
Normal file
5
projectpromote/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* ProjectPromote - An integrated dependency on my projects promotion for my own use.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/TSBattery
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/9/13.
|
||||
*/
|
||||
package com.fankes.projectpromote
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.html.HtmlPlugin
|
||||
import io.noties.markwon.image.ImagesPlugin
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 项目推广工具类
|
||||
*/
|
||||
object ProjectPromote {
|
||||
|
||||
/** 推广链接的主要地址 (EN) */
|
||||
private const val PROJECT_PROMOTE_MAIN_EN_URL = "https://raw.githubusercontent.com/fankes/fankes/main/project-promote/README.md"
|
||||
|
||||
/** 推广链接的主要地址 (CN) */
|
||||
private const val PROJECT_PROMOTE_MAIN_CN_URL = "https://raw.gitmirror.com/fankes/fankes/main/project-promote/README-zh-CN.md"
|
||||
|
||||
/** 推广链接的代理地址 (EN) */
|
||||
private const val PROJECT_PROMOTE_PROXY_EN_URL = "https://raw.githubusercontent.com/fankes/fankes/main/project-promote/README.md"
|
||||
|
||||
/** 推广链接的代理地址 (CN) */
|
||||
private const val PROJECT_PROMOTE_PROXY_CN_URL = "https://raw.gitmirror.com/fankes/fankes/main/project-promote/README-zh-CN.md"
|
||||
|
||||
/** 记录的 Sp 名称 */
|
||||
private const val PROJECT_PROMOTE_SP_NAME = "project_promote_readed"
|
||||
|
||||
/** 记录的 Sp 已读键值名称 */
|
||||
private const val PROJECT_PROMOTE_SP_IS_READ = "is_read"
|
||||
|
||||
/**
|
||||
* 展示推广对话框
|
||||
* @param activity 当前实例
|
||||
* @param tag 当前标签 - 用于非重复展示对话框
|
||||
* @param dismissSeconds “不再提示” 按钮可点击的秒数 - 默认 10 秒
|
||||
*/
|
||||
fun show(activity: ComponentActivity, tag: String, dismissSeconds: Int = 10) {
|
||||
val sp = createSp(activity, tag)
|
||||
if (sp.getBoolean(PROJECT_PROMOTE_SP_IS_READ, false)) return
|
||||
var isEnableButton1 = false
|
||||
var isEnableButton2 = false
|
||||
val dialog = createDialog(activity)
|
||||
dialog.show()
|
||||
val messageView = dialog.findViewById<TextView>(android.R.id.message) ?: error("Missing message text")
|
||||
val button1 = dialog.findViewById<Button>(android.R.id.button1) ?: error("Missing button 1")
|
||||
val button2 = dialog.findViewById<Button>(android.R.id.button2) ?: error("Missing button 2")
|
||||
button1.isEnabled = false
|
||||
button2.isEnabled = false
|
||||
button1.setOnClickListener { if (isEnableButton1) dialog.dismiss() }
|
||||
button2.setOnClickListener {
|
||||
if (isEnableButton2.not()) return@setOnClickListener
|
||||
sp.edit { putBoolean(PROJECT_PROMOTE_SP_IS_READ, true) }
|
||||
dialog.dismiss()
|
||||
}
|
||||
val markwon = createMarkdown(activity)
|
||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
/**
|
||||
* 执行解析逻辑
|
||||
* @param isDone 是否成功
|
||||
* @param content 内容
|
||||
*/
|
||||
suspend fun doParse(isDone: Boolean, content: String) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isDone) {
|
||||
markwon.setMarkdown(messageView, content)
|
||||
messageView.invalidate()
|
||||
messageView.requestLayout()
|
||||
} else messageView.text = activity.getString(R.string.promote_dialog_message_fail)
|
||||
button1.text = activity.getText(R.string.promote_dialog_button_1)
|
||||
button2.text = ""
|
||||
button1.isEnabled = true
|
||||
isEnableButton1 = true
|
||||
}
|
||||
if (isDone) for (seconds in dismissSeconds downTo 0) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val button2Text = activity.getString(R.string.promote_dialog_button_2)
|
||||
button2.text = if (seconds > 0) "$button2Text ($seconds)" else button2Text
|
||||
button2.isEnabled = seconds == 0
|
||||
isEnableButton2 = seconds == 0
|
||||
}; delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
val mainUrl = if (isSystemLanguageSimplifiedChinese) PROJECT_PROMOTE_MAIN_CN_URL else PROJECT_PROMOTE_MAIN_EN_URL
|
||||
val proxyUrl = if (isSystemLanguageSimplifiedChinese) PROJECT_PROMOTE_PROXY_CN_URL else PROJECT_PROMOTE_PROXY_EN_URL
|
||||
loadContentFromUrl(mainUrl) { isDone, content ->
|
||||
if (isDone) doParse(isDone = true, content)
|
||||
else loadContentFromUrl(proxyUrl) { isDone2, content2 -> doParse(isDone2, content2) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Sp 存储
|
||||
* @param activity 当前实例
|
||||
* @param tag 当前标签
|
||||
* @return [SharedPreferences]
|
||||
*/
|
||||
private fun createSp(activity: Activity, tag: String) = activity.getSharedPreferences("${PROJECT_PROMOTE_SP_NAME}_$tag", Context.MODE_PRIVATE)
|
||||
|
||||
/**
|
||||
* 创建对话框
|
||||
* @param activity 当前实例
|
||||
* @return [AlertDialog]
|
||||
*/
|
||||
private fun createDialog(activity: Activity) =
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setCancelable(false)
|
||||
.setTitle(activity.getString(R.string.promote_dialog_title))
|
||||
.setMessage(activity.getString(R.string.promote_dialog_message_loading))
|
||||
.setPositiveButton("...", null)
|
||||
.setNegativeButton("...", null)
|
||||
.create()
|
||||
|
||||
/**
|
||||
* 创建 [Markwon]
|
||||
* @param activity 当前实例
|
||||
* @return [Markwon]
|
||||
*/
|
||||
private fun createMarkdown(activity: Activity) =
|
||||
Markwon.builder(activity)
|
||||
.usePlugin(ImagesPlugin.create())
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.build()
|
||||
|
||||
/**
|
||||
* 从 URL 读取内容
|
||||
* @param url 当前 URL
|
||||
* @param onResponse 回调结果 - ([Boolean] 是否成功,[String] 内容)
|
||||
*/
|
||||
private suspend fun loadContentFromUrl(url: String, onResponse: suspend (isDone: Boolean, content: String) -> Unit) {
|
||||
runCatching {
|
||||
val response = OkHttpClient().newCall(Request.Builder().url(url).build()).execute()
|
||||
val content = response.body?.string() ?: ""
|
||||
onResponse(response.isSuccessful && content.isNotBlank(), content)
|
||||
}.onFailure { onResponse(false, "") }
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前系统环境是否为简体中文
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val isSystemLanguageSimplifiedChinese
|
||||
get(): Boolean {
|
||||
val locale = Locale.getDefault()
|
||||
return locale.language == "zh" && locale.country == "CN"
|
||||
}
|
||||
}
|
18
projectpromote/src/main/res/layout/adapter_node.xml
Normal file
18
projectpromote/src/main/res/layout/adapter_node.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:breakStrategy="simple"
|
||||
android:hyphenationFrequency="none"
|
||||
android:lineSpacingExtra="2dip"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="#000"
|
||||
android:textSize="16sp"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:text="Hello" />
|
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:fillViewport="true"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip"
|
||||
android:scrollbarStyle="outsideInset">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#0f000000"
|
||||
android:fontFamily="monospace"
|
||||
android:lineSpacingExtra="2dip"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingRight="16dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="14sp" />
|
||||
</HorizontalScrollView>
|
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="#000"
|
||||
android:textSize="16sp"
|
||||
tools:text="Table content" />
|
8
projectpromote/src/main/res/values-zh/strings.xml
Normal file
8
projectpromote/src/main/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="promote_dialog_title">项目推广</string>
|
||||
<string name="promote_dialog_message_loading">内容正在加载中...</string>
|
||||
<string name="promote_dialog_button_1">我知道了</string>
|
||||
<string name="promote_dialog_button_2">不再提示</string>
|
||||
<string name="promote_dialog_message_fail">内容加载失败,可能是网络错误。</string>
|
||||
</resources>
|
8
projectpromote/src/main/res/values/strings.xml
Normal file
8
projectpromote/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="promote_dialog_title">Project Promote</string>
|
||||
<string name="promote_dialog_message_loading">Content is loading...</string>
|
||||
<string name="promote_dialog_button_1">Got It</string>
|
||||
<string name="promote_dialog_button_2">Dismiss</string>
|
||||
<string name="promote_dialog_message_fail">The content failed to load, possibly due to a network error.</string>
|
||||
</resources>
|
@@ -0,0 +1,18 @@
|
||||
package com.fankes.projectpromote
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user