diff --git a/OpenNSFW/src/main/AndroidManifest.xml b/OpenNSFW/src/main/AndroidManifest.xml
index 79cf2019..1b8cb6c5 100644
--- a/OpenNSFW/src/main/AndroidManifest.xml
+++ b/OpenNSFW/src/main/AndroidManifest.xml
@@ -1,2 +1 @@
-
+
diff --git a/README.md b/README.md
index 5e73bae7..85c3e4f5 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,11 @@
### JavaScript参考[JS相关文档](https://js.tensorflow.org/api/latest/)
>>> 其中Python、C++均有两种数据喂入的方式,可根据需求选择pb模型或tfLite文件,详细请参考上面的链接.Java的目前只能加载pb模型。其他的平台可自行[百度](https://www.baidu.com)
### [pb模型下载](https://github.com/devzwy/NSFW-Python/tree/master/model)
-### 本项目移除测试图片,请下载Demo后自行配图测试
+### 本项目移除测试图片,请下载Demo后自行配图测试
+#### 即将优化Demo使用MVVM模式,可用作开发脚手架使用
+`Kotlin+okhttp3+rxjava2+retrofit2+koin+glide+greendao+databinding+Livedata`
+
+
### 使用
- Add it in your root build.gradle at the end of repositories:
diff --git a/app/build.gradle b/app/build.gradle
index 78bc1aac..ef5e31da 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -58,4 +58,8 @@ dependencies {
// implementation project(path: ':nsfw')
// implementation project(path: ':OpenNSFW')
implementation 'com.github.devzwy:open_nsfw_android:1.3.0'
+
+ implementation 'com.squareup.okhttp3:okhttp:4.7.2'
+ implementation "org.koin:koin-android:2.1.5"
+ implementation 'com.google.code.gson:gson:2.8.6'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2dc38f43..a9ae5727 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,32 +1,34 @@
+ package="com.example.open_nsfw_android">
-
-
-
-
+
+
+
+
+ android:label="@string/title_activity_main2" />
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/open_nsfw_android/DemoApplication.kt b/app/src/main/java/com/example/open_nsfw_android/DemoApplication.kt
index 5c8f58b8..9886c340 100644
--- a/app/src/main/java/com/example/open_nsfw_android/DemoApplication.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/DemoApplication.kt
@@ -1,15 +1,32 @@
package com.example.open_nsfw_android
import android.app.Application
+import com.example.open_nsfw_android.view.mModules
import com.zwy.opennsfw.core.Classifier
+import okhttp3.OkHttp
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidFileProperties
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
+import org.koin.core.logger.PrintLogger
+import org.koin.core.qualifier.qualifier
+import org.koin.dsl.module
class DemoApplication : Application() {
override fun onCreate() {
super.onCreate()
+
+ startKoin {
+ modules(mModules)
+ }
Classifier.Build()
.context(this) //必须调用 否则会有异常抛出
// .isOpenGPU(true)//默认不开启GPU加速 部分机型开启会奔溃,自行选择,默认false
// .numThreads(10) //分配的线程数 根据手机配置设置,默认1
.build()
+
}
-}
\ No newline at end of file
+
+}
diff --git a/app/src/main/java/com/example/open_nsfw_android/util/MainAdapter.kt b/app/src/main/java/com/example/open_nsfw_android/util/MainAdapter.kt
index 148beaa3..73b85ed7 100644
--- a/app/src/main/java/com/example/open_nsfw_android/util/MainAdapter.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/util/MainAdapter.kt
@@ -5,9 +5,12 @@ import android.support.v4.content.ContextCompat
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
+import com.bumptech.glide.Glide
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.example.open_nsfw_android.R
+import getNsfwScore
+import kotlinx.android.synthetic.main.activity_main2.*
import java.math.RoundingMode
import java.text.DecimalFormat
@@ -15,12 +18,14 @@ class MainAdapter(nsfwList: List?) :
BaseQuickAdapter(R.layout.main_item, nsfwList) {
@SuppressLint("SetTextI18n")
- override fun convert(helper: BaseViewHolder, item: MyNsfwBean) {
+ override fun convert(helper: BaseViewHolder, item2: MyNsfwBean) {
+
val textView = helper.getView(R.id.tv_text)
val imageView = helper.getView(R.id.iv)
val view = helper.getView(R.id.view)
+
var color = ContextCompat.getColor(mContext, R.color.nsfw1)
- when (item.nsfw) {
+ when (item2.nsfw) {
in 0.0..0.2 -> {
color = ContextCompat.getColor(mContext, R.color.nsfw3)
}
@@ -33,8 +38,14 @@ class MainAdapter(nsfwList: List?) :
floatFormat.roundingMode = RoundingMode.HALF_UP
textView.text =
- "\n适宜度(非色情程度):${floatFormat.format(item.sfw * 100)}%\n\n不适宜度(涉黄程度):${floatFormat.format(item.nsfw * 100)}%"
- imageView.setImageBitmap(item.bitmap)
+ "\n适宜度(非色情程度):${floatFormat.format(item2.sfw * 100)}%\n\n不适宜度(涉黄程度):${floatFormat.format(
+ item2.nsfw * 100
+ )}%"
+ if (item2.imgUrl != null && item2.imgUrl.isNotBlank()) {
+ Glide.with(imageView).load(item2.imgUrl).into(imageView)
+ } else {
+ imageView.setImageBitmap(item2.bitmap)
+ }
view.setBackgroundColor(color)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/open_nsfw_android/util/kt.kt b/app/src/main/java/com/example/open_nsfw_android/util/kt.kt
index c9855c57..2d19ba2b 100644
--- a/app/src/main/java/com/example/open_nsfw_android/util/kt.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/util/kt.kt
@@ -2,4 +2,10 @@ package com.example.open_nsfw_android.util
import android.graphics.Bitmap
-data class MyNsfwBean(var sfw: Float, var nsfw: Float, val path: String, val bitmap: Bitmap)
\ No newline at end of file
+data class MyNsfwBean(
+ var sfw: Float,
+ var nsfw: Float,
+ val path: String,
+ val bitmap: Bitmap?,
+ val imgUrl: String? = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/open_nsfw_android/view/CameraActivity.kt b/app/src/main/java/com/example/open_nsfw_android/view/CameraActivity.kt
index 6a86339c..8e8508fd 100644
--- a/app/src/main/java/com/example/open_nsfw_android/view/CameraActivity.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/view/CameraActivity.kt
@@ -182,17 +182,17 @@ class CameraActivity : AppCompatActivity(), SurfaceHolder.Callback, Camera.Previ
@SuppressLint("SetTextI18n")
override fun onPreviewFrame(data: ByteArray?, camera: Camera?) {
- mCamera.addCallbackBuffer(data)
-
- val size = mCamera.parameters.previewSize;
- val image = YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
- val stream = ByteArrayOutputStream()
- image.compressToJpeg(Rect(0, 0, size.width, size.height), 80, stream)
- val bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size())
- bmp.getNsfwScore()
- val spText = bmp.getNsfwScore().toString().split(",")
- textView.text = spText[0] + "\n" + spText[1]
- stream.close()
+ mCamera.addCallbackBuffer(data)
+
+ val size = mCamera.parameters.previewSize;
+ val image = YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
+ val stream = ByteArrayOutputStream()
+ image.compressToJpeg(Rect(0, 0, size.width, size.height), 80, stream)
+ val bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size())
+ bmp.getNsfwScore()
+ val spText = bmp.getNsfwScore().toString().split(",")
+ textView.text = spText[0] + "\n" + spText[1]
+ stream.close()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/open_nsfw_android/view/MainAty.kt b/app/src/main/java/com/example/open_nsfw_android/view/MainAty.kt
index f39972c5..bd0f0ecf 100644
--- a/app/src/main/java/com/example/open_nsfw_android/view/MainAty.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/view/MainAty.kt
@@ -5,24 +5,30 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
-import android.graphics.Camera
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.widget.Toast
+import com.bumptech.glide.Glide
import com.example.open_nsfw_android.R
import com.example.open_nsfw_android.util.MainAdapter
import com.example.open_nsfw_android.util.MyNsfwBean
import com.example.open_nsfw_android.util.PackageUtils
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
import com.luck.picture.lib.PictureSelector
import com.luck.picture.lib.config.PictureConfig
import com.luck.picture.lib.config.PictureMimeType
import com.luck.picture.lib.entity.LocalMedia
import getNsfwScore
import kotlinx.android.synthetic.main.activity_main.*
+import okhttp3.*
+import org.koin.android.ext.android.inject
import java.io.File
+import java.io.IOException
+import java.lang.reflect.Type
class MainAty : AppCompatActivity() {
@@ -30,6 +36,11 @@ class MainAty : AppCompatActivity() {
lateinit var mMainAdapter: MainAdapter
+ val httpClient: OkHttpClient by inject()
+
+ val gson: Gson by inject()
+
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -48,11 +59,62 @@ class MainAty : AppCompatActivity() {
//识别assets目录
bt_sc_assets.setOnClickListener { assetsListSc() }
//选择相册进行扫描
- bt_sc_from_other.setOnClickListener { selectImgFromD() }
+ bt_sc_from_other.setOnClickListener { selectImgFromD(0x01) }
//跳转网络图片识别页面
- bt_sc_from_internet.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) }
+ bt_sc_from_internet.setOnClickListener {
+ startActivity(
+ Intent(
+ this,
+ Main2Activity::class.java
+ )
+ )
+ }
//实时扫描
bt_sc_from_cam.setOnClickListener { scCamera() }
+ //调用Api获取测试图片
+ bt_sc_from_api.setOnClickListener {
+ httpClient.newCall(Request.Builder().get().url(GetImageListURL).build())
+ .enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ runOnUiThread {
+ Toast.makeText(this@MainAty, "请求失败,请重试", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val str = response.body?.string()
+ val type: Type? = object : TypeToken>() {}.type
+ val list = gson.fromJson>(str, type)
+ val l = ArrayList()
+ //迭代数据并获取涉黄程度
+ list.forEach {
+ System.out.println(it)
+ val file = Glide.with(this@MainAty)
+ .load(it.imgUrl)
+ .downloadOnly(120, 120).get()
+ val nsfwBean = file.getNsfwScore()
+ //填充数据到适配器
+ l.add(
+ MyNsfwBean(
+ sfw = nsfwBean.sfw,
+ nsfw = nsfwBean.nsfw,
+ path = it.createTime,
+ imgUrl = it.imgUrl,
+ bitmap = null
+ )
+ )
+ }
+ runOnUiThread {
+ mMainAdapter.setNewData(null)
+ mMainAdapter.setNewData(l)
+ }
+ }
+ })
+ }
+
+ bt_upload.setOnClickListener {
+ selectImgFromD(0x02)
+ }
tv_version.text = "当前版本号:${PackageUtils.getVersionName(this)}"
}
@@ -75,7 +137,7 @@ class MainAty : AppCompatActivity() {
/**
* 相册选择
*/
- private fun selectImgFromD() {
+ private fun selectImgFromD(requestCode: Int) {
PictureSelector.create(this)
.openGallery(PictureMimeType.ofImage())//全部.ofAll()、图片.、视频.ofVideo()、音频.ofAudio()
.maxSelectNum(20)// 最大图片选择数量 int
@@ -88,7 +150,7 @@ class MainAty : AppCompatActivity() {
.selectionMedia(selectList)
.sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效
.previewEggs(true)// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false
- .forResult(0x01);//结果回调onActivityResult code }
+ .forResult(requestCode);//结果回调onActivityResult code }
}
/**
@@ -127,7 +189,11 @@ class MainAty : AppCompatActivity() {
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
- ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1);
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ 1
+ );
}
if (ContextCompat.checkSelfPermission(
@@ -164,6 +230,29 @@ class MainAty : AppCompatActivity() {
}).start()
}
+ if (requestCode == 0x02 && resultCode == RESULT_OK) {
+ selectList = PictureSelector.obtainMultipleResult(data)
+ val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
+// selectList?.let {
+// mMainAdapter.setNewData(null)
+// Toast.makeText(this, "请稍等...", Toast.LENGTH_LONG).show()
+// }
+// Thread(Runnable {
+// selectList?.forEach {
+// val file = File(it.path)
+// val nsfwScore = file.getNsfwScore()
+// addDataToAdapter(
+// MyNsfwBean(
+// nsfwScore.sfw,
+// nsfwScore.nsfw,
+// it.path,
+// BitmapFactory.decodeStream(file.inputStream())
+// )
+// )
+// }
+// }).start()
+
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/open_nsfw_android/view/kt.kt b/app/src/main/java/com/example/open_nsfw_android/view/kt.kt
new file mode 100644
index 00000000..bfae1d67
--- /dev/null
+++ b/app/src/main/java/com/example/open_nsfw_android/view/kt.kt
@@ -0,0 +1,32 @@
+package com.example.open_nsfw_android.view
+
+import com.google.gson.Gson
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.koin.core.qualifier.qualifier
+import org.koin.dsl.module
+
+val BaseUrl = "http://114.116.213.202:7777/"
+val GetImageListURL = "${BaseUrl}imageList.do"
+val UploadImageURL = "${BaseUrl}imageList.do"
+
+
+val mModules = module {
+ single {
+ OkHttpClient.Builder().build()
+ }
+ single {
+ Gson()
+ }
+}
+
+
+data class NSFWImage(
+ val createTime: String,
+ val id: Int,
+ val imgUrl: String
+) {
+ override fun toString(): String {
+ return Gson().toJson(this)
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
index 5768f2e9..e86fe0a9 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -18,10 +18,10 @@
android:type="linear">
+ android:offset="0.0" />
+ android:offset="1.0" />
@@ -30,5 +30,5 @@
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
- android:strokeColor="#00000000"/>
+ android:strokeColor="#00000000" />
diff --git a/app/src/main/res/drawable/ch.xml b/app/src/main/res/drawable/ch.xml
index 841bdd4f..144c1d8a 100644
--- a/app/src/main/res/drawable/ch.xml
+++ b/app/src/main/res/drawable/ch.xml
@@ -1,23 +1,29 @@
-
-
-
-
-
-
-
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="1148.6849"
+ android:viewportHeight="1148.6849"
+ android:tint="#4749FF">
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/change_background.xml b/app/src/main/res/drawable/change_background.xml
index ca3826a4..a5ce813b 100644
--- a/app/src/main/res/drawable/change_background.xml
+++ b/app/src/main/res/drawable/change_background.xml
@@ -1,74 +1,170 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml
index 63cb8d91..b91bca99 100644
--- a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml
@@ -1,5 +1,11 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_camera_black_24dp.xml b/app/src/main/res/drawable/ic_camera_black_24dp.xml
index 7a5c6cfb..ac0302eb 100644
--- a/app/src/main/res/drawable/ic_camera_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_camera_black_24dp.xml
@@ -1,5 +1,11 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index bb1921d6..e2561079 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -6,165 +6,165 @@
android:viewportHeight="108">
+ android:pathData="M0,0h108v108h-108z" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
+ android:strokeColor="#33FFFFFF" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 700b643e..acbe5d76 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,96 +1,136 @@
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main2.xml b/app/src/main/res/layout/activity_main2.xml
index 3a06dbbe..07ebf156 100644
--- a/app/src/main/res/layout/activity_main2.xml
+++ b/app/src/main/res/layout/activity_main2.xml
@@ -1,10 +1,10 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".Main2Activity">
+ android:padding="15dp" />
+ android:text="识别" />
+ android:background="#504d4d4d" />
+ android:text="这里显示检测结果" />
+ android:padding="15dp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_item.xml b/app/src/main/res/layout/main_item.xml
index d8172cd2..388f6018 100644
--- a/app/src/main/res/layout/main_item.xml
+++ b/app/src/main/res/layout/main_item.xml
@@ -1,22 +1,22 @@
+ android:layout_height="wrap_content">
+ android:layout_height="wrap_content" />
+ android:layout_alignParentRight="true" />
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/change.xml b/app/src/main/res/mipmap-anydpi-v26/change.xml
index 457fa69d..74f4a902 100644
--- a/app/src/main/res/mipmap-anydpi-v26/change.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/change.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/change_round.xml b/app/src/main/res/mipmap-anydpi-v26/change_round.xml
index 457fa69d..74f4a902 100644
--- a/app/src/main/res/mipmap-anydpi-v26/change_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/change_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index bbd3e021..eca70cfe 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index bbd3e021..eca70cfe 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 177cefc8..545b9c6d 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -13,8 +13,8 @@
- true
-
+
-
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 00000000..2439f15c
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 9da3a6de..fe86a72f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,10 +3,12 @@ buildscript {
repositories {
google()
jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.3'
+ classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
}
}
diff --git a/demo/.gitignore b/demo/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/demo/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/demo/build.gradle b/demo/build.gradle
new file mode 100644
index 00000000..0842a86c
--- /dev/null
+++ b/demo/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'org.greenrobot.greendao'
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ applicationId "com.zwy.demo"
+ minSdkVersion 19
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.3.1"
+ multiDexEnabled true
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ dataBinding {
+ enabled = true
+ }
+
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ greendao {
+ schemaVersion 1
+ daoPackage 'com.zwy.demo.db'
+ targetGenDir 'src/main/java'
+ }
+
+ aaptOptions {
+ noCompress "tflite"
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ testImplementation 'junit:junit:4.13'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+ implementation "org.koin:koin-android:2.1.5"
+ implementation "org.koin:koin-androidx-viewmodel:2.1.5"
+ implementation "org.koin:koin-androidx-ext:2.1.5"
+ implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"
+
+ //retrofit
+ implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
+ implementation 'com.squareup.retrofit2:retrofit:2.8.2'
+ implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
+ implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.2'
+
+ //沉浸式状态栏
+ implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
+ //适配器快速实现
+ implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
+ //图片加载框架
+ implementation 'com.github.bumptech.glide:glide:4.11.0'
+ kapt 'com.github.bumptech.glide:compiler:4.11.0'
+
+ implementation 'org.greenrobot:greendao:3.3.0'
+ implementation 'org.greenrobot:greendao-generator:3.3.0'
+ implementation 'com.android.support:multidex:1.0.3'
+ implementation "org.jetbrains.anko:anko:0.10.5"
+
+ //NSFW鉴黄库
+ implementation 'com.github.devzwy:open_nsfw_android:1.3.0'
+ implementation 'pub.devrel:easypermissions:3.0.0'
+
+ implementation 'com.github.LuckSiege.PictureSelector:picture_library:2.2.5'
+}
diff --git a/demo/proguard-rules.pro b/demo/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/demo/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/demo/src/androidTest/java/com/zwy/demo/ExampleInstrumentedTest.kt b/demo/src/androidTest/java/com/zwy/demo/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..5018f9cc
--- /dev/null
+++ b/demo/src/androidTest/java/com/zwy/demo/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.zwy.demo
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * 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.zwy.demo", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..100a86bd
--- /dev/null
+++ b/demo/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/assets/img/test_img1.jpeg b/demo/src/main/assets/img/test_img1.jpeg
new file mode 100644
index 00000000..2503c3c2
Binary files /dev/null and b/demo/src/main/assets/img/test_img1.jpeg differ
diff --git a/demo/src/main/assets/img/test_img2.jpeg b/demo/src/main/assets/img/test_img2.jpeg
new file mode 100644
index 00000000..616a2478
Binary files /dev/null and b/demo/src/main/assets/img/test_img2.jpeg differ
diff --git a/demo/src/main/assets/img/test_img3.jpg b/demo/src/main/assets/img/test_img3.jpg
new file mode 100644
index 00000000..fbd6f09b
Binary files /dev/null and b/demo/src/main/assets/img/test_img3.jpg differ
diff --git a/demo/src/main/java/com/zwy/demo/NSFWApplication.kt b/demo/src/main/java/com/zwy/demo/NSFWApplication.kt
new file mode 100644
index 00000000..c19460d4
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/NSFWApplication.kt
@@ -0,0 +1,50 @@
+package com.zwy.demo
+
+import android.content.Context
+import androidx.multidex.MultiDexApplication
+import com.zwy.demo.utils.ActivityLifecycleCallback
+import com.zwy.demo.utils.networkModule
+import com.zwy.demo.utils.repositoryModule
+import com.zwy.demo.utils.viewModelModules
+import com.zwy.opennsfw.core.Classifier
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
+import java.util.*
+
+class NSFWApplication : MultiDexApplication() {
+
+
+ override fun onCreate() {
+ super.onCreate()
+
+ startTime = Date().time
+
+ context = this
+
+ //全局配置actionbar title
+ registerActivityLifecycleCallbacks(ActivityLifecycleCallback())
+
+ //初始化鉴黄库NSFW
+ Classifier.Build()
+ .context(this) //必须调用 否则会有异常抛出
+// .isOpenGPU(true)//默认不开启GPU加速 部分机型开启会奔溃,自行选择,默认false
+// .numThreads(1) //分配的线程数 根据手机配置设置,默认1
+ .build()
+ //全局注入对象
+ startKoin {
+ androidContext(this@NSFWApplication)
+ androidLogger()
+ modules(networkModule)
+ modules(repositoryModule)
+ modules(viewModelModules)
+ }
+
+
+ }
+
+ companion object {
+ lateinit var context: Context
+ var startTime: Long = 0
+ }
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/base/BaseActivity.kt b/demo/src/main/java/com/zwy/demo/base/BaseActivity.kt
new file mode 100644
index 00000000..0ccd4f90
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/base/BaseActivity.kt
@@ -0,0 +1,111 @@
+package com.zwy.demo.base
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.Observer
+import com.gyf.immersionbar.ImmersionBar
+import com.zwy.demo.R
+import org.jetbrains.anko.indeterminateProgressDialog
+import org.koin.java.KoinJavaComponent.get
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+abstract class BaseActivity : AppCompatActivity(),
+ DialogInterface.OnCancelListener {
+
+ lateinit var binding: V
+ lateinit var viewModel: VM
+ var dialog: Dialog? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ImmersionBar.with(this).fitsSystemWindows(true).statusBarColor(R.color.colorPrimary).init()
+ initViewDataBinding(savedInstanceState)
+ registorUIChangeLiveDataCallBack()
+ initData()
+ }
+
+
+ /**
+ * 注入绑定
+ */
+ private fun initViewDataBinding(savedInstanceState: Bundle?) {
+ binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState))
+ val type: Type = javaClass.genericSuperclass!!
+ viewModel = get((type as ParameterizedType).actualTypeArguments[1] as Class)
+ binding.lifecycleOwner = this
+ }
+
+ /**
+ * 注册ViewModel与View的契约UI回调事件
+ */
+ protected open fun registorUIChangeLiveDataCallBack() {
+
+ viewModel.appLiveData.observe(this, Observer {
+
+ when (it.type) {
+ 0 -> {
+ //加载进度条
+ if (it.text.isNotBlank()) initDialog(it.text)
+ dialog?.show()
+ }
+ 1 -> {
+ //取消进度条
+ if (dialog != null && dialog?.isShowing == true) {
+ dialog?.dismiss()
+ }
+ }
+ 2 -> {
+ //关闭页面
+ finish()
+ }
+
+ }
+
+ })
+ }
+
+
+ /**
+ *
+ * 父类onCreate执行完成后调用
+ */
+ abstract fun initData()
+
+ /**
+ * 初始化根布局
+ *
+ * @return 布局layout的id
+ */
+ abstract fun initContentView(savedInstanceState: Bundle?): Int
+
+
+ /**
+ * 初始化进度条
+ */
+ private fun initDialog(msg: String) {
+ dialog = indeterminateProgressDialog(msg, "提示")
+ dialog?.setCanceledOnTouchOutside(false)
+ dialog?.setOnCancelListener(this)
+ }
+
+ /**
+ * 页面进度条对话框被取消后的回掉
+ */
+ override fun onCancel(dialog: DialogInterface?) {
+ Log.d(com.zwy.demo.utils.TAG, "进度条销毁回掉")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding.unbind()
+ }
+
+
+}
+
diff --git a/demo/src/main/java/com/zwy/demo/base/BaseViewModel.kt b/demo/src/main/java/com/zwy/demo/base/BaseViewModel.kt
new file mode 100644
index 00000000..646cd85a
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/base/BaseViewModel.kt
@@ -0,0 +1,32 @@
+package com.zwy.demo.base
+
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.zwy.demo.utils.AppLiveData
+import com.zwy.demo.utils.TAG
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.disposables.Disposable
+
+
+open class BaseViewModel : ViewModel() {
+ val appLiveData = MutableLiveData()
+
+ private var mCompositeDisposable: CompositeDisposable? = null
+
+ /**
+ * 将所有请求加入集合,页面推出后统一取消
+ */
+ fun addSubscribe(subscribe: Disposable) {
+ if (mCompositeDisposable == null) mCompositeDisposable = CompositeDisposable()
+ mCompositeDisposable!!.add(subscribe)
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ Log.d(TAG, "页面销毁,取消全部网络请求")
+ if (mCompositeDisposable != null) mCompositeDisposable!!.clear()
+ }
+
+
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/db/DaoMaster.java b/demo/src/main/java/com/zwy/demo/db/DaoMaster.java
new file mode 100644
index 00000000..0531ccc2
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/db/DaoMaster.java
@@ -0,0 +1,99 @@
+package com.zwy.demo.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.util.Log;
+
+import org.greenrobot.greendao.AbstractDaoMaster;
+import org.greenrobot.greendao.database.StandardDatabase;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseOpenHelper;
+import org.greenrobot.greendao.identityscope.IdentityScopeType;
+
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/**
+ * Master of DAO (schema version 1): knows all DAOs.
+ */
+public class DaoMaster extends AbstractDaoMaster {
+ public static final int SCHEMA_VERSION = 1;
+
+ /** Creates underlying database table using DAOs. */
+ public static void createAllTables(Database db, boolean ifNotExists) {
+ HomeTitleDao.createTable(db, ifNotExists);
+ ImageBeanDao.createTable(db, ifNotExists);
+ }
+
+ /** Drops underlying database table using DAOs. */
+ public static void dropAllTables(Database db, boolean ifExists) {
+ HomeTitleDao.dropTable(db, ifExists);
+ ImageBeanDao.dropTable(db, ifExists);
+ }
+
+ /**
+ * WARNING: Drops all table on Upgrade! Use only during development.
+ * Convenience method using a {@link DevOpenHelper}.
+ */
+ public static DaoSession newDevSession(Context context, String name) {
+ Database db = new DevOpenHelper(context, name).getWritableDb();
+ DaoMaster daoMaster = new DaoMaster(db);
+ return daoMaster.newSession();
+ }
+
+ public DaoMaster(SQLiteDatabase db) {
+ this(new StandardDatabase(db));
+ }
+
+ public DaoMaster(Database db) {
+ super(db, SCHEMA_VERSION);
+ registerDaoClass(HomeTitleDao.class);
+ registerDaoClass(ImageBeanDao.class);
+ }
+
+ public DaoSession newSession() {
+ return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
+ }
+
+ public DaoSession newSession(IdentityScopeType type) {
+ return new DaoSession(db, type, daoConfigMap);
+ }
+
+ /**
+ * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
+ */
+ public static abstract class OpenHelper extends DatabaseOpenHelper {
+ public OpenHelper(Context context, String name) {
+ super(context, name, SCHEMA_VERSION);
+ }
+
+ public OpenHelper(Context context, String name, CursorFactory factory) {
+ super(context, name, factory, SCHEMA_VERSION);
+ }
+
+ @Override
+ public void onCreate(Database db) {
+ Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
+ createAllTables(db, false);
+ }
+ }
+
+ /** WARNING: Drops all table on Upgrade! Use only during development. */
+ public static class DevOpenHelper extends OpenHelper {
+ public DevOpenHelper(Context context, String name) {
+ super(context, name);
+ }
+
+ public DevOpenHelper(Context context, String name, CursorFactory factory) {
+ super(context, name, factory);
+ }
+
+ @Override
+ public void onUpgrade(Database db, int oldVersion, int newVersion) {
+ Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
+ dropAllTables(db, true);
+ onCreate(db);
+ }
+ }
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/db/DaoSession.java b/demo/src/main/java/com/zwy/demo/db/DaoSession.java
new file mode 100644
index 00000000..5daa32b7
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/db/DaoSession.java
@@ -0,0 +1,62 @@
+package com.zwy.demo.db;
+
+import java.util.Map;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.AbstractDaoSession;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.identityscope.IdentityScopeType;
+import org.greenrobot.greendao.internal.DaoConfig;
+
+import com.zwy.demo.dbbean.HomeTitle;
+import com.zwy.demo.dbbean.ImageBean;
+
+import com.zwy.demo.db.HomeTitleDao;
+import com.zwy.demo.db.ImageBeanDao;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+
+/**
+ * {@inheritDoc}
+ *
+ * @see org.greenrobot.greendao.AbstractDaoSession
+ */
+public class DaoSession extends AbstractDaoSession {
+
+ private final DaoConfig homeTitleDaoConfig;
+ private final DaoConfig imageBeanDaoConfig;
+
+ private final HomeTitleDao homeTitleDao;
+ private final ImageBeanDao imageBeanDao;
+
+ public DaoSession(Database db, IdentityScopeType type, Map>, DaoConfig>
+ daoConfigMap) {
+ super(db);
+
+ homeTitleDaoConfig = daoConfigMap.get(HomeTitleDao.class).clone();
+ homeTitleDaoConfig.initIdentityScope(type);
+
+ imageBeanDaoConfig = daoConfigMap.get(ImageBeanDao.class).clone();
+ imageBeanDaoConfig.initIdentityScope(type);
+
+ homeTitleDao = new HomeTitleDao(homeTitleDaoConfig, this);
+ imageBeanDao = new ImageBeanDao(imageBeanDaoConfig, this);
+
+ registerDao(HomeTitle.class, homeTitleDao);
+ registerDao(ImageBean.class, imageBeanDao);
+ }
+
+ public void clear() {
+ homeTitleDaoConfig.clearIdentityScope();
+ imageBeanDaoConfig.clearIdentityScope();
+ }
+
+ public HomeTitleDao getHomeTitleDao() {
+ return homeTitleDao;
+ }
+
+ public ImageBeanDao getImageBeanDao() {
+ return imageBeanDao;
+ }
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/db/HomeTitleDao.java b/demo/src/main/java/com/zwy/demo/db/HomeTitleDao.java
new file mode 100644
index 00000000..dc15fc1e
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/db/HomeTitleDao.java
@@ -0,0 +1,129 @@
+package com.zwy.demo.db;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.Property;
+import org.greenrobot.greendao.internal.DaoConfig;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseStatement;
+
+import com.zwy.demo.dbbean.HomeTitle;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/**
+ * DAO for table "HOME_TITLE".
+*/
+public class HomeTitleDao extends AbstractDao {
+
+ public static final String TABLENAME = "HOME_TITLE";
+
+ /**
+ * Properties of entity HomeTitle.
+ * Can be used for QueryBuilder and for referencing column names.
+ */
+ public static class Properties {
+ public final static Property Id = new Property(0, Long.class, "id", true, "_id");
+ public final static Property Title = new Property(1, String.class, "title", false, "TITLE");
+ }
+
+
+ public HomeTitleDao(DaoConfig config) {
+ super(config);
+ }
+
+ public HomeTitleDao(DaoConfig config, DaoSession daoSession) {
+ super(config, daoSession);
+ }
+
+ /** Creates the underlying database table. */
+ public static void createTable(Database db, boolean ifNotExists) {
+ String constraint = ifNotExists? "IF NOT EXISTS ": "";
+ db.execSQL("CREATE TABLE " + constraint + "\"HOME_TITLE\" (" + //
+ "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
+ "\"TITLE\" TEXT);"); // 1: title
+ }
+
+ /** Drops the underlying database table. */
+ public static void dropTable(Database db, boolean ifExists) {
+ String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"HOME_TITLE\"";
+ db.execSQL(sql);
+ }
+
+ @Override
+ protected final void bindValues(DatabaseStatement stmt, HomeTitle entity) {
+ stmt.clearBindings();
+
+ Long id = entity.getId();
+ if (id != null) {
+ stmt.bindLong(1, id);
+ }
+
+ String title = entity.getTitle();
+ if (title != null) {
+ stmt.bindString(2, title);
+ }
+ }
+
+ @Override
+ protected final void bindValues(SQLiteStatement stmt, HomeTitle entity) {
+ stmt.clearBindings();
+
+ Long id = entity.getId();
+ if (id != null) {
+ stmt.bindLong(1, id);
+ }
+
+ String title = entity.getTitle();
+ if (title != null) {
+ stmt.bindString(2, title);
+ }
+ }
+
+ @Override
+ public Long readKey(Cursor cursor, int offset) {
+ return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0);
+ }
+
+ @Override
+ public HomeTitle readEntity(Cursor cursor, int offset) {
+ HomeTitle entity = new HomeTitle( //
+ cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
+ cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1) // title
+ );
+ return entity;
+ }
+
+ @Override
+ public void readEntity(Cursor cursor, HomeTitle entity, int offset) {
+ entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
+ entity.setTitle(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
+ }
+
+ @Override
+ protected final Long updateKeyAfterInsert(HomeTitle entity, long rowId) {
+ entity.setId(rowId);
+ return rowId;
+ }
+
+ @Override
+ public Long getKey(HomeTitle entity) {
+ if(entity != null) {
+ return entity.getId();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean hasKey(HomeTitle entity) {
+ return entity.getId() != null;
+ }
+
+ @Override
+ protected final boolean isEntityUpdateable() {
+ return true;
+ }
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/db/ImageBeanDao.java b/demo/src/main/java/com/zwy/demo/db/ImageBeanDao.java
new file mode 100644
index 00000000..24e6d442
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/db/ImageBeanDao.java
@@ -0,0 +1,171 @@
+package com.zwy.demo.db;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.Property;
+import org.greenrobot.greendao.internal.DaoConfig;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseStatement;
+
+import com.zwy.demo.dbbean.ImageBean;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/**
+ * DAO for table "IMAGE_BEAN".
+*/
+public class ImageBeanDao extends AbstractDao {
+
+ public static final String TABLENAME = "IMAGE_BEAN";
+
+ /**
+ * Properties of entity ImageBean.
+ * Can be used for QueryBuilder and for referencing column names.
+ */
+ public static class Properties {
+ public final static Property CreateTime = new Property(0, String.class, "createTime", false, "CREATE_TIME");
+ public final static Property Id = new Property(1, Long.class, "id", true, "_id");
+ public final static Property ImgUrl = new Property(2, String.class, "imgUrl", false, "IMG_URL");
+ public final static Property Nsfw = new Property(3, Float.class, "nsfw", false, "NSFW");
+ public final static Property Sfw = new Property(4, Float.class, "sfw", false, "SFW");
+ }
+
+
+ public ImageBeanDao(DaoConfig config) {
+ super(config);
+ }
+
+ public ImageBeanDao(DaoConfig config, DaoSession daoSession) {
+ super(config, daoSession);
+ }
+
+ /** Creates the underlying database table. */
+ public static void createTable(Database db, boolean ifNotExists) {
+ String constraint = ifNotExists? "IF NOT EXISTS ": "";
+ db.execSQL("CREATE TABLE " + constraint + "\"IMAGE_BEAN\" (" + //
+ "\"CREATE_TIME\" TEXT," + // 0: createTime
+ "\"_id\" INTEGER PRIMARY KEY ," + // 1: id
+ "\"IMG_URL\" TEXT," + // 2: imgUrl
+ "\"NSFW\" REAL," + // 3: nsfw
+ "\"SFW\" REAL);"); // 4: sfw
+ }
+
+ /** Drops the underlying database table. */
+ public static void dropTable(Database db, boolean ifExists) {
+ String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"IMAGE_BEAN\"";
+ db.execSQL(sql);
+ }
+
+ @Override
+ protected final void bindValues(DatabaseStatement stmt, ImageBean entity) {
+ stmt.clearBindings();
+
+ String createTime = entity.getCreateTime();
+ if (createTime != null) {
+ stmt.bindString(1, createTime);
+ }
+
+ Long id = entity.getId();
+ if (id != null) {
+ stmt.bindLong(2, id);
+ }
+
+ String imgUrl = entity.getImgUrl();
+ if (imgUrl != null) {
+ stmt.bindString(3, imgUrl);
+ }
+
+ Float nsfw = entity.getNsfw();
+ if (nsfw != null) {
+ stmt.bindDouble(4, nsfw);
+ }
+
+ Float sfw = entity.getSfw();
+ if (sfw != null) {
+ stmt.bindDouble(5, sfw);
+ }
+ }
+
+ @Override
+ protected final void bindValues(SQLiteStatement stmt, ImageBean entity) {
+ stmt.clearBindings();
+
+ String createTime = entity.getCreateTime();
+ if (createTime != null) {
+ stmt.bindString(1, createTime);
+ }
+
+ Long id = entity.getId();
+ if (id != null) {
+ stmt.bindLong(2, id);
+ }
+
+ String imgUrl = entity.getImgUrl();
+ if (imgUrl != null) {
+ stmt.bindString(3, imgUrl);
+ }
+
+ Float nsfw = entity.getNsfw();
+ if (nsfw != null) {
+ stmt.bindDouble(4, nsfw);
+ }
+
+ Float sfw = entity.getSfw();
+ if (sfw != null) {
+ stmt.bindDouble(5, sfw);
+ }
+ }
+
+ @Override
+ public Long readKey(Cursor cursor, int offset) {
+ return cursor.isNull(offset + 1) ? null : cursor.getLong(offset + 1);
+ }
+
+ @Override
+ public ImageBean readEntity(Cursor cursor, int offset) {
+ ImageBean entity = new ImageBean( //
+ cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // createTime
+ cursor.isNull(offset + 1) ? null : cursor.getLong(offset + 1), // id
+ cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // imgUrl
+ cursor.isNull(offset + 3) ? null : cursor.getFloat(offset + 3), // nsfw
+ cursor.isNull(offset + 4) ? null : cursor.getFloat(offset + 4) // sfw
+ );
+ return entity;
+ }
+
+ @Override
+ public void readEntity(Cursor cursor, ImageBean entity, int offset) {
+ entity.setCreateTime(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
+ entity.setId(cursor.isNull(offset + 1) ? null : cursor.getLong(offset + 1));
+ entity.setImgUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
+ entity.setNsfw(cursor.isNull(offset + 3) ? null : cursor.getFloat(offset + 3));
+ entity.setSfw(cursor.isNull(offset + 4) ? null : cursor.getFloat(offset + 4));
+ }
+
+ @Override
+ protected final Long updateKeyAfterInsert(ImageBean entity, long rowId) {
+ entity.setId(rowId);
+ return rowId;
+ }
+
+ @Override
+ public Long getKey(ImageBean entity) {
+ if(entity != null) {
+ return entity.getId();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean hasKey(ImageBean entity) {
+ return entity.getId() != null;
+ }
+
+ @Override
+ protected final boolean isEntityUpdateable() {
+ return true;
+ }
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/dbbean/HomeTitle.java b/demo/src/main/java/com/zwy/demo/dbbean/HomeTitle.java
new file mode 100644
index 00000000..dfbde47d
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/dbbean/HomeTitle.java
@@ -0,0 +1,42 @@
+package com.zwy.demo.dbbean;
+
+import org.greenrobot.greendao.annotation.Entity;
+import org.greenrobot.greendao.annotation.Generated;
+import org.greenrobot.greendao.annotation.Id;
+
+@Entity
+public class HomeTitle {
+ @Id
+ private Long id;
+ private String title;
+
+ @Generated(hash = 2016918462)
+ public HomeTitle(Long id, String title) {
+ this.id = id;
+ this.title = title;
+ }
+
+ @Generated(hash = 869810785)
+ public HomeTitle() {
+ }
+
+ public HomeTitle(String title) {
+ this.title = title;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
diff --git a/demo/src/main/java/com/zwy/demo/dbbean/ImageBean.java b/demo/src/main/java/com/zwy/demo/dbbean/ImageBean.java
new file mode 100644
index 00000000..3354527e
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/dbbean/ImageBean.java
@@ -0,0 +1,78 @@
+package com.zwy.demo.dbbean;
+
+import org.greenrobot.greendao.annotation.Entity;
+import org.greenrobot.greendao.annotation.Generated;
+import org.greenrobot.greendao.annotation.Id;
+
+@Entity
+public class ImageBean {
+ private String createTime;
+ @Id
+ private Long id;
+ private String imgUrl = "";
+ private Float nsfw = 0.0f;
+ private Float sfw = 0.0f;
+
+
+ @Generated(hash = 716432991)
+ public ImageBean(String createTime, Long id, String imgUrl, Float nsfw,
+ Float sfw) {
+ this.createTime = createTime;
+ this.id = id;
+ this.imgUrl = imgUrl;
+ this.nsfw = nsfw;
+ this.sfw = sfw;
+ }
+
+ public ImageBean(String imgUrl, Float nsfw,
+ Float sfw) {
+ this.imgUrl = imgUrl;
+ this.nsfw = nsfw;
+ this.sfw = sfw;
+ }
+
+ @Generated(hash = 645668394)
+ public ImageBean() {
+ }
+
+ public String getCreateTime() {
+ return this.createTime;
+ }
+
+ public void setCreateTime(String createTime) {
+ this.createTime = createTime;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getImgUrl() {
+ return this.imgUrl;
+ }
+
+ public void setImgUrl(String imgUrl) {
+ this.imgUrl = imgUrl;
+ }
+
+ public Float getNsfw() {
+ return this.nsfw;
+ }
+
+ public void setNsfw(Float nsfw) {
+ this.nsfw = nsfw;
+ }
+
+ public Float getSfw() {
+ return this.sfw;
+ }
+
+ public void setSfw(Float sfw) {
+ this.sfw = sfw;
+ }
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/models/MainViewModel.kt b/demo/src/main/java/com/zwy/demo/models/MainViewModel.kt
new file mode 100644
index 00000000..8eda5444
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/models/MainViewModel.kt
@@ -0,0 +1,19 @@
+package com.zwy.demo.models
+
+import androidx.lifecycle.MutableLiveData
+import com.zwy.demo.base.BaseViewModel
+import com.zwy.demo.dbbean.HomeTitle
+import com.zwy.demo.repositorys.AppRepository
+
+class MainViewModel constructor(val mAppRepository: AppRepository) :
+ BaseViewModel() {
+
+ var titles = MutableLiveData>()
+
+
+ fun getTitles() {
+ this.titles.value = mAppRepository.getHomeTitles()
+ }
+}
+
+
diff --git a/demo/src/main/java/com/zwy/demo/models/ScanPicModel.kt b/demo/src/main/java/com/zwy/demo/models/ScanPicModel.kt
new file mode 100644
index 00000000..c8460fa5
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/models/ScanPicModel.kt
@@ -0,0 +1,87 @@
+package com.zwy.demo.models
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import android.graphics.BitmapFactory
+import androidx.lifecycle.MutableLiveData
+import com.bumptech.glide.Glide
+import com.zwy.demo.NSFWApplication
+import com.zwy.demo.base.BaseViewModel
+import com.zwy.demo.dbbean.ImageBean
+import com.zwy.demo.repositorys.AppRepository
+import com.zwy.demo.utils.AppLiveData
+import getNsfwScore
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import org.koin.java.KoinJavaComponent.inject
+
+class ScanPicViewModel constructor(val mAppRepository: AppRepository) :
+ BaseViewModel() {
+
+
+ var imgData = MutableLiveData>()
+
+ val context: Context by inject(Application::class.java)
+
+
+ @SuppressLint("CheckResult")
+ fun startScann(pageIndex: Int) {
+ mAppRepository.startScann(pageIndex)
+ .subscribeOn(Schedulers.io())
+ .doOnSubscribe {
+ addSubscribe(it)
+ appLiveData.value =
+ AppLiveData(0, if (pageIndex == 0x03) "多图下载较慢,请耐心等待......." else "描中请稍后.......")
+ }
+ .map {
+ return@map when (pageIndex) {
+ 0x01 -> {
+ it.filter {
+ val bitmap =
+ BitmapFactory.decodeStream(context.resources.assets.open(it.imgUrl))
+ val nsfwScore = bitmap.getNsfwScore()
+ it.nsfw = nsfwScore.nsfw
+ it.sfw = nsfwScore.sfw
+ bitmap.recycle()
+ true
+ }
+ }
+ 0x02 -> {
+ it
+ }
+ 0x03 -> {
+ it.filter {
+ val nsfwBean =
+ Glide.with(NSFWApplication.context).asBitmap().load(it.imgUrl)
+ .submit().get().getNsfwScore()
+ it.sfw = nsfwBean.sfw
+ it.nsfw = nsfwBean.nsfw
+ true
+ }
+ if (it.size > 10) it.subList(it.size - 10, it.size)
+ else it
+
+ }
+ else -> {
+ it
+ }
+
+ }
+
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ appLiveData.value = AppLiveData(1, "")
+ imgData.value = it
+ }, {
+ appLiveData.value = AppLiveData(1, "")
+ print(it)
+ })
+
+ }
+
+
+}
+
+
diff --git a/demo/src/main/java/com/zwy/demo/repositorys/ApiService.kt b/demo/src/main/java/com/zwy/demo/repositorys/ApiService.kt
new file mode 100644
index 00000000..330654d8
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/repositorys/ApiService.kt
@@ -0,0 +1,16 @@
+package com.zwy.demo.repositorys
+
+import com.zwy.demo.dbbean.ImageBean
+import io.reactivex.Observable
+import retrofit2.http.GET
+
+/**
+ * 网络请求类
+ */
+interface ApiService {
+
+ @GET("/imageList.do")
+ fun getImageList(): Observable>
+
+
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/repositorys/AppRepository.kt b/demo/src/main/java/com/zwy/demo/repositorys/AppRepository.kt
new file mode 100644
index 00000000..2f707d68
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/repositorys/AppRepository.kt
@@ -0,0 +1,56 @@
+package com.zwy.demo.repositorys
+
+import android.annotation.SuppressLint
+import android.app.Application
+import com.google.gson.Gson
+import com.zwy.demo.dbbean.HomeTitle
+import com.zwy.demo.dbbean.ImageBean
+import com.zwy.demo.utils.DBHelper
+import io.reactivex.Observable
+import org.koin.java.KoinJavaComponent.inject
+
+/**
+ * 数据源 这里决定是从缓存获取还是从网络获取
+ */
+class AppRepository constructor(
+ private val mApiService: ApiService,
+ private val mDBHelper: DBHelper,
+ val mGson: Gson
+) :
+ Repository {
+
+ val context: Application by inject(Application::class.java)
+
+ /**
+ * 获取首页list标题
+ */
+ override fun getHomeTitles(): List = mDBHelper.getTitles()
+
+ /**
+ * 扫描图片
+ */
+ @SuppressLint("CheckResult")
+ override fun startScann(index: Int): Observable> {
+
+ return when (index) {
+ 0x01 -> {
+ val list = arrayListOf()
+ context.resources.assets.list("img")!!.forEach {
+ list.add(ImageBean("img/$it", 0.0f, 0.0f))
+ }
+ Observable.just(list)
+ }
+ 0x02 -> {
+ Observable.just(emptyList())
+ }
+ 0x03 -> {
+ mApiService.getImageList()
+ }
+ else -> {
+ Observable.just(emptyList())
+ }
+ }
+ }
+
+}
+
diff --git a/demo/src/main/java/com/zwy/demo/repositorys/Repository.kt b/demo/src/main/java/com/zwy/demo/repositorys/Repository.kt
new file mode 100644
index 00000000..5d364358
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/repositorys/Repository.kt
@@ -0,0 +1,17 @@
+package com.zwy.demo.repositorys
+
+import com.zwy.demo.dbbean.HomeTitle
+import com.zwy.demo.dbbean.ImageBean
+import io.reactivex.Observable
+
+interface Repository {
+ /**
+ * 获取首页list标题
+ */
+ fun getHomeTitles(): List
+
+ /**
+ * 扫描图片
+ */
+ fun startScann(index: Int): Observable>
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/ActivityLifecycleCallback.kt b/demo/src/main/java/com/zwy/demo/utils/ActivityLifecycleCallback.kt
new file mode 100644
index 00000000..0a190dfa
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/ActivityLifecycleCallback.kt
@@ -0,0 +1,40 @@
+package com.zwy.demo.utils
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
+import com.zwy.demo.R
+
+class ActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
+ override fun onActivityPaused(activity: Activity) {
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ }
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ activity.findViewById(R.id.actionBarTitle)?.text = activity.title
+
+ activity.findViewById(R.id.iv_back).also {
+ if (activity.title == "离线鉴黄") it?.visibility = View.GONE
+ it?.setOnClickListener {
+ activity.finish()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/Constant.kt b/demo/src/main/java/com/zwy/demo/utils/Constant.kt
new file mode 100644
index 00000000..5cdbc29d
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/Constant.kt
@@ -0,0 +1,17 @@
+package com.zwy.demo.utils
+
+/**
+ * 常量 请求超时时间
+ */
+const val NETWORK_TIMEOUT = 60L
+
+/**
+ * 常量 baseurl
+ */
+const val BASE_URL = "http://114.116.213.202:7777"
+
+
+const val TAG = "鉴黄Demo日志"
+
+
+const val ParmKey2ScanPicAty = "ParmKey2ScanPicAty"
diff --git a/demo/src/main/java/com/zwy/demo/utils/DBHelper.kt b/demo/src/main/java/com/zwy/demo/utils/DBHelper.kt
new file mode 100644
index 00000000..d12d010f
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/DBHelper.kt
@@ -0,0 +1,64 @@
+package com.zwy.demo.utils
+
+import com.zwy.demo.NSFWApplication
+import com.zwy.demo.db.DaoMaster
+import com.zwy.demo.db.DaoSession
+import com.zwy.demo.dbbean.HomeTitle
+
+
+/**
+ * 数据库操作类
+ */
+class DBHelper private constructor(val mDaoSession: DaoSession) {
+
+ /**
+ * 获取全部已缓存的图片列表
+ */
+ fun getImageListFromDB() = mDaoSession.imageBeanDao.queryBuilder().list()
+
+ /**
+ * 获取首页列表数据
+ */
+ fun getTitles() = mDaoSession.homeTitleDao.queryBuilder().list()
+
+ companion object {
+ private var instance: DBHelper? = null
+ get() {
+ if (field == null) {
+ field = DBHelper(initDB())
+ }
+ return field
+ }
+
+ private fun initDB(): DaoSession {
+ return DaoMaster(
+ DaoMaster.DevOpenHelper(
+ NSFWApplication.context,
+ "nsfw_db"
+ ).writableDatabase
+ ).newSession().apply {
+ //插入默认数据
+ if (this.homeTitleDao.queryBuilder().list().size == 0) {
+ arrayListOf(
+ "识别Assets目录图片",
+ "识别相册图片",
+ "实时扫描识别",
+ "测试网络图片库中的大尺度资源",
+ "我有资源贡献",
+ "如果项目对您有帮助",
+ "请给我一个Star吧"
+ ).forEach {
+ System.out.println("数据写入成功")
+ this.homeTitleDao.insert(HomeTitle(it))
+ }
+ }
+ }
+ }
+
+ @Synchronized
+ fun get(): DBHelper {
+ return instance!!
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/DataBindingAdapter.kt b/demo/src/main/java/com/zwy/demo/utils/DataBindingAdapter.kt
new file mode 100644
index 00000000..1fac3e65
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/DataBindingAdapter.kt
@@ -0,0 +1,144 @@
+package com.zwy.demo.utils
+
+import android.content.Intent
+import android.net.Uri
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.cardview.widget.CardView
+import androidx.databinding.BindingAdapter
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.chad.library.adapter.base.BaseQuickAdapter
+import com.chad.library.adapter.base.animation.ScaleInAnimation
+import com.chad.library.adapter.base.viewholder.BaseViewHolder
+import com.zwy.demo.R
+import com.zwy.demo.dbbean.HomeTitle
+import com.zwy.demo.dbbean.ImageBean
+import com.zwy.demo.views.ScanPicActivity
+import org.jetbrains.anko.startActivity
+
+
+/**
+ * 首页列表适配器填充
+ */
+@BindingAdapter("bindHomeData")
+fun RecyclerView.bindData(data: List?) {
+
+ val mContext = this.context
+
+ val homeAdapter =
+ object :
+ BaseQuickAdapter(R.layout.tem_home, data?.toMutableList()) {
+ override fun convert(holder: BaseViewHolder, item: HomeTitle) {
+ holder.setText(R.id.tv_title, item.title)
+ }
+ }
+ homeAdapter.animationEnable = true
+ homeAdapter.isAnimationFirstOnly = false
+ homeAdapter.adapterAnimation = ScaleInAnimation()
+
+ homeAdapter.addHeaderView(LayoutInflater.from(mContext)
+ .inflate(R.layout.item_home_head, LinearLayout(mContext)).apply {
+ findViewById(R.id.tv_info).text =
+ mContext.getString(R.string.versionstr, PackageUtils.getVersionName(mContext))
+ })
+
+ homeAdapter.addFooterView(LayoutInflater.from(mContext)
+ .inflate(R.layout.item_home_head, LinearLayout(mContext)).apply {
+ this.setPadding(20, 0, 20, 0)
+ findViewById(R.id.tv_info).apply {
+ setTextColor(mContext.resources.getColor(R.color.red))
+ text = context.getString(R.string.nsfw_info)
+ }
+ })
+
+ this.layoutManager = LinearLayoutManager(mContext)
+ this.adapter = homeAdapter
+
+ homeAdapter.setOnItemClickListener { adapter, view, position ->
+ when (position) {
+ 0 -> {
+ //识别Assets目录
+ mContext.startActivity(ParmKey2ScanPicAty to 0x01)
+ }
+ 1 -> {
+ //识别相册
+ mContext.startActivity(ParmKey2ScanPicAty to 0x02)
+ }
+ 2 -> {
+ //实时扫描
+ }
+ 3 -> {
+ //Api获取图片测试
+ mContext.startActivity(ParmKey2ScanPicAty to 0x03)
+ }
+ 4 -> {
+ //上传文件
+ }
+ 5 -> {
+ //跳转GitHub首页
+ mContext.startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("https://github.com/devzwy/open_nsfw_android")
+ )
+ )
+ }
+ 6 -> {
+ //跳转GitHub首页
+ mContext.startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("https://github.com/devzwy/open_nsfw_android")
+ )
+ )
+ }
+
+ }
+ }
+}
+
+
+/**
+ * 首页列表适配器填充
+ */
+@BindingAdapter("bindScanPicListData")
+fun RecyclerView.bindData2(data: List?) {
+
+ val mContext = this.context
+
+ val pathBase = "file:///android_asset/"
+ val scannAdapter =
+ object :
+ BaseQuickAdapter(
+ R.layout.item_scann,
+ data?.toMutableList()
+ ) {
+ override fun convert(holder: BaseViewHolder, item: ImageBean) {
+ holder.getView(R.id.tv_sss).apply {
+ text = "NSFW 分值:${item.nsfw} \nSFW 分值:${item.sfw}"
+// textColorResource = if (item.nsfw > 0.5) R.color.white else R.color.colorPrimary
+ }
+ holder.getView(R.id.cardview)
+ .setBackgroundResource(if (item.nsfw > 0.5) R.color.nsfw else R.color.colorPrimary)
+ val imageView = holder.getView(R.id.iv_item)
+ if (item.imgUrl.contains("http")) {
+ Glide.with(imageView).load(item.imgUrl).into(imageView)
+ } else {
+ Glide.with(imageView).load("${pathBase}${item.imgUrl}").into(imageView)
+ }
+ }
+ }
+ scannAdapter.animationEnable = true
+ scannAdapter.isAnimationFirstOnly = false
+ scannAdapter.adapterAnimation = ScaleInAnimation()
+
+
+ this.layoutManager = LinearLayoutManager(mContext)
+ this.adapter = scannAdapter
+
+}
+
diff --git a/demo/src/main/java/com/zwy/demo/utils/PackageUtils.kt b/demo/src/main/java/com/zwy/demo/utils/PackageUtils.kt
new file mode 100644
index 00000000..24687972
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/PackageUtils.kt
@@ -0,0 +1,77 @@
+package com.zwy.demo.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+
+object PackageUtils {
+ /**
+ * 获取版本名称
+ *
+ * @param context 上下文
+ *
+ * @return 版本名称
+ */
+ fun getVersionName(context: Context): String? {
+
+ //获取包管理器
+ val pm = context.packageManager
+ //获取包信息
+ try {
+ val packageInfo =
+ pm.getPackageInfo(context.packageName, 0)
+ //返回版本号
+ return packageInfo.versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+
+ /**
+ * 获取版本号
+ *
+ * @param context 上下文
+ *
+ * @return 版本号
+ */
+ fun getVersionCode(context: Context): Int {
+
+ //获取包管理器
+ val pm = context.packageManager
+ //获取包信息
+ try {
+ val packageInfo =
+ pm.getPackageInfo(context.packageName, 0)
+ //返回版本号
+ return packageInfo.versionCode
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ return 0
+ }
+
+ /**
+ * 获取App的名称
+ *
+ * @param context 上下文
+ *
+ * @return 名称
+ */
+ fun getAppName(context: Context): String? {
+ val pm = context.packageManager
+ //获取包信息
+ try {
+ val packageInfo =
+ pm.getPackageInfo(context.packageName, 0)
+ //获取应用 信息
+ val applicationInfo = packageInfo.applicationInfo
+ //获取albelRes
+ val labelRes = applicationInfo.labelRes
+ //返回App的名称
+ return context.resources.getString(labelRes)
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/bean.kt b/demo/src/main/java/com/zwy/demo/utils/bean.kt
new file mode 100644
index 00000000..0790c9a3
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/bean.kt
@@ -0,0 +1,4 @@
+package com.zwy.demo.utils
+
+
+data class AppLiveData(var type: Int = -1, var text: String = "")
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/kt.kt b/demo/src/main/java/com/zwy/demo/utils/kt.kt
new file mode 100644
index 00000000..cb60caa8
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/kt.kt
@@ -0,0 +1,25 @@
+package com.zwy.demo.utils
+
+import android.app.Activity
+import com.luck.picture.lib.PictureSelector
+import com.luck.picture.lib.config.PictureConfig
+import com.luck.picture.lib.config.PictureMimeType
+
+/**
+ * 相册选择
+ */
+fun Activity.selectImgFromD(requestCode: Int) {
+ PictureSelector.create(this)
+ .openGallery(PictureMimeType.ofImage())//全部.ofAll()、图片.、视频.ofVideo()、音频.ofAudio()
+ .maxSelectNum(if (requestCode == 0) 20 else 1)// 最大图片选择数量 int
+ .minSelectNum(1)// 最小选择数量 int
+ .imageSpanCount(3)// 每行显示个数 int
+ .selectionMode(PictureConfig.MULTIPLE)// 多选 or 单选 or PictureConfig.SINGLE
+ .previewImage(true)// 是否可预览图片 true or false
+ .isCamera(false)// 是否显示拍照按钮 true or false
+ .isZoomAnim(true)// 图片列表点击 缩放效果 默认true
+// .selectionMedia(selectList)
+ .sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效
+ .previewEggs(true)// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false
+ .forResult(requestCode);//结果回调onActivityResult code }
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/utils/modules.kt b/demo/src/main/java/com/zwy/demo/utils/modules.kt
new file mode 100644
index 00000000..f6813866
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/utils/modules.kt
@@ -0,0 +1,64 @@
+package com.zwy.demo.utils
+
+import com.google.gson.Gson
+import com.zwy.demo.models.MainViewModel
+import com.zwy.demo.models.ScanPicViewModel
+import com.zwy.demo.repositorys.ApiService
+import com.zwy.demo.repositorys.AppRepository
+import io.reactivex.schedulers.Schedulers
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+
+val networkModule = module {
+ single {
+ OkHttpClient().newBuilder()
+ .connectTimeout(NETWORK_TIMEOUT, TimeUnit.SECONDS)
+ .readTimeout(NETWORK_TIMEOUT, TimeUnit.SECONDS)
+ .writeTimeout(NETWORK_TIMEOUT, TimeUnit.SECONDS)
+ .addInterceptor(
+ HttpLoggingInterceptor()
+ .setLevel(HttpLoggingInterceptor.Level.BODY)
+ )
+ .build()
+ }
+
+ single {
+ Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(get())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ single {
+ get().create(ApiService::class.java)
+ }
+
+ single {
+ Gson()
+ }
+
+}
+
+val repositoryModule = module {
+ single {
+ AppRepository(get(), DBHelper.get(), get())
+ }
+}
+
+val viewModelModules = module {
+ viewModel {
+ ScanPicViewModel(get())
+ }
+ viewModel {
+ MainViewModel(get())
+ }
+}
\ No newline at end of file
diff --git a/demo/src/main/java/com/zwy/demo/views/MainActivity.kt b/demo/src/main/java/com/zwy/demo/views/MainActivity.kt
new file mode 100644
index 00000000..a15033e0
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/views/MainActivity.kt
@@ -0,0 +1,36 @@
+package com.zwy.demo.views
+
+import android.os.Bundle
+import com.zwy.demo.R
+import com.zwy.demo.base.BaseActivity
+import com.zwy.demo.databinding.MainLayoutBinding
+import com.zwy.demo.models.MainViewModel
+import org.jetbrains.anko.toast
+
+class MainActivity : BaseActivity() {
+ /**
+ * 初始化根布局
+ *
+ * @return 布局layout的id
+ */
+ override fun initContentView(savedInstanceState: Bundle?): Int = R.layout.activity_main
+
+
+ override fun initData() {
+ binding.titles = viewModel.titles
+ viewModel.getTitles()
+ }
+
+ private var mExitTime: Long = 0
+
+ override fun onBackPressed() {
+ if (System.currentTimeMillis() - mExitTime < 2000) {
+ super.onBackPressed();
+ } else {
+ mExitTime = System.currentTimeMillis();
+ toast("再按一次返回键退出应用")
+ }
+ }
+
+
+}
diff --git a/demo/src/main/java/com/zwy/demo/views/ScanPicActivity.kt b/demo/src/main/java/com/zwy/demo/views/ScanPicActivity.kt
new file mode 100644
index 00000000..8b9859cb
--- /dev/null
+++ b/demo/src/main/java/com/zwy/demo/views/ScanPicActivity.kt
@@ -0,0 +1,77 @@
+package com.zwy.demo.views
+
+import android.Manifest
+import android.os.Bundle
+import com.zwy.demo.R
+import com.zwy.demo.base.BaseActivity
+import com.zwy.demo.databinding.ScanPicBindingView
+import com.zwy.demo.models.ScanPicViewModel
+import com.zwy.demo.utils.ParmKey2ScanPicAty
+import com.zwy.demo.utils.selectImgFromD
+import pub.devrel.easypermissions.EasyPermissions
+import pub.devrel.easypermissions.PermissionRequest
+
+class ScanPicActivity : BaseActivity() {
+
+ val requestCode = 0x01
+ private fun _requestPermissions() {
+ if (!EasyPermissions.hasPermissions(
+ this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ )
+ ) {
+ EasyPermissions.requestPermissions(
+ PermissionRequest.Builder(
+ this,
+ 0x01,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ).build()
+ )
+ return
+ }
+ }
+
+ override fun initData() {
+ _requestPermissions()
+ binding.dataList = viewModel.imgData
+ intent.getIntExtra(ParmKey2ScanPicAty, 0x00).let {
+ if (it == 1 || it == 3)
+ viewModel.startScann(it)
+ else selectImgFromD(requestCode)
+ }
+ }
+
+ /**
+ * 初始化根布局
+ *
+ * @return 布局layout的id
+ */
+ override fun initContentView(savedInstanceState: Bundle?): Int = R.layout.activity_scan_pic
+
+
+// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+// super.onActivityResult(requestCode, resultCode, data)
+// if (requestCode == 0x01 && resultCode == RESULT_OK) {
+// PictureSelector.obtainMultipleResult(data)?.let {
+// mMainAdapter.setNewData(null)
+// Toast.makeText(this, "请稍等...", Toast.LENGTH_LONG).show()
+// }
+// Thread(Runnable {
+// selectList?.forEach {
+// val file = File(it.path)
+// val nsfwScore = file.getNsfwScore()
+// addDataToAdapter(
+// MyNsfwBean(
+// nsfwScore.sfw,
+// nsfwScore.nsfw,
+// it.path,
+// BitmapFactory.decodeStream(file.inputStream())
+// )
+// )
+// }
+// }).start()
+//
+// }
+// }
+}
+
diff --git a/demo/src/main/res/drawable-anydpi/ic_back.xml b/demo/src/main/res/drawable-anydpi/ic_back.xml
new file mode 100644
index 00000000..dce9c968
--- /dev/null
+++ b/demo/src/main/res/drawable-anydpi/ic_back.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/demo/src/main/res/drawable-hdpi/ic_back.png b/demo/src/main/res/drawable-hdpi/ic_back.png
new file mode 100644
index 00000000..7ea0ee85
Binary files /dev/null and b/demo/src/main/res/drawable-hdpi/ic_back.png differ
diff --git a/demo/src/main/res/drawable-mdpi/ic_back.png b/demo/src/main/res/drawable-mdpi/ic_back.png
new file mode 100644
index 00000000..11d840cb
Binary files /dev/null and b/demo/src/main/res/drawable-mdpi/ic_back.png differ
diff --git a/demo/src/main/res/drawable-xhdpi/ic_back.png b/demo/src/main/res/drawable-xhdpi/ic_back.png
new file mode 100644
index 00000000..6fcc749b
Binary files /dev/null and b/demo/src/main/res/drawable-xhdpi/ic_back.png differ
diff --git a/demo/src/main/res/drawable-xxhdpi/ic_back.png b/demo/src/main/res/drawable-xxhdpi/ic_back.png
new file mode 100644
index 00000000..988c5e2b
Binary files /dev/null and b/demo/src/main/res/drawable-xxhdpi/ic_back.png differ
diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..c21d549e
--- /dev/null
+++ b/demo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/layout/activity_scan_pic.xml b/demo/src/main/res/layout/activity_scan_pic.xml
new file mode 100644
index 00000000..c857cb16
--- /dev/null
+++ b/demo/src/main/res/layout/activity_scan_pic.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/layout/include_actionbar.xml b/demo/src/main/res/layout/include_actionbar.xml
new file mode 100644
index 00000000..da089fd1
--- /dev/null
+++ b/demo/src/main/res/layout/include_actionbar.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/layout/item_home_head.xml b/demo/src/main/res/layout/item_home_head.xml
new file mode 100644
index 00000000..622ab876
--- /dev/null
+++ b/demo/src/main/res/layout/item_home_head.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/layout/item_scann.xml b/demo/src/main/res/layout/item_scann.xml
new file mode 100644
index 00000000..54f4da33
--- /dev/null
+++ b/demo/src/main/res/layout/item_scann.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/layout/tem_home.xml b/demo/src/main/res/layout/tem_home.xml
new file mode 100644
index 00000000..279330cc
--- /dev/null
+++ b/demo/src/main/res/layout/tem_home.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/mipmap-xhdpi/launch.png b/demo/src/main/res/mipmap-xhdpi/launch.png
new file mode 100644
index 00000000..ecde7e5b
Binary files /dev/null and b/demo/src/main/res/mipmap-xhdpi/launch.png differ
diff --git a/demo/src/main/res/values/colors.xml b/demo/src/main/res/values/colors.xml
new file mode 100644
index 00000000..3e605fe3
--- /dev/null
+++ b/demo/src/main/res/values/colors.xml
@@ -0,0 +1,14 @@
+
+
+ #2196F3
+ #00B0FF
+ #03DAC5
+ #FAFAFA
+ #FAFAFA
+ #202020
+ #F30606
+ #50202020
+
+ #F33D03
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml
new file mode 100644
index 00000000..03fc80a8
--- /dev/null
+++ b/demo/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+ Demo
+ 离线鉴黄
+ 所有图片均来源百度,如有侵权请联系我删除
+ 网络接收图像并给出输出概率(分数在0-1之间),该概率可用于过滤不适合工作图像的图像。得分小于 0.2表示该图像很可能是安全的。得分大于 0.8表示该图像很有可能是NSFW。中间范围的分数可以针对不同的NSFW级别进行分类。
+根据数据集,用例和图像类型,我们建议开发人员选择合适的阈值。由于问题的困难性质,会出现错误,具体取决于NSFW的用例/定义/容忍度。理想情况下,开发人员应根据对他们的应用程序安全的定义来创建评估集,然后按原样使用模型拟合ROC曲线以选择合适的阈值。
+ 当前最新版本:%s
+ 自定义扫描
+
\ No newline at end of file
diff --git a/demo/src/main/res/values/styles.xml b/demo/src/main/res/values/styles.xml
new file mode 100644
index 00000000..836a0a2a
--- /dev/null
+++ b/demo/src/main/res/values/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/main/res/xml/network_security_config.xml b/demo/src/main/res/xml/network_security_config.xml
new file mode 100644
index 00000000..2439f15c
--- /dev/null
+++ b/demo/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/demo/src/test/java/com/zwy/demo/ExampleUnitTest.kt b/demo/src/test/java/com/zwy/demo/ExampleUnitTest.kt
new file mode 100644
index 00000000..336c332e
--- /dev/null
+++ b/demo/src/test/java/com/zwy/demo/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.zwy.demo
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index a201e9aa..3f8f6b56 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,16 +1,5 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
-kotlin.coroutines=enable
\ No newline at end of file
+kotlin.coroutines=enable
+#android.useAndroidX=true
+#android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1657edf5..0551da47 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Apr 19 20:19:15 CST 2020
+#Thu Jun 04 08:01:30 CST 2020
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/img/3.jpeg b/img/3.jpeg
new file mode 100644
index 00000000..0fa9a322
Binary files /dev/null and b/img/3.jpeg differ
diff --git a/settings.gradle b/settings.gradle
index b9b85c61..8709124c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,3 @@
-include ':app', ':OpenNSFW'
+//include ':demo' //开启新demo必须注释 include ':app' 并打开gradle.properties中 注释的两行代码 因为新demo使用了androidx与app模块冲突 demo模块完成:Assets目录识别、网络图片识别
+include ':app'
+include ':OpenNSFW'