diff --git a/mirai-demos/mirai-demo-android/build.gradle b/mirai-demos/mirai-demo-android/build.gradle
index 1462c5d05..2da33aac9 100644
--- a/mirai-demos/mirai-demo-android/build.gradle
+++ b/mirai-demos/mirai-demo-android/build.gradle
@@ -1,17 +1,17 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.multiplatform'
+ id 'kotlin-android-extensions'
}
android {
compileSdkVersion 29
defaultConfig {
applicationId "net.mamoe.mirai.demo"
- minSdkVersion 23
+ minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -53,10 +53,11 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
def anko_version = "0.10.8"
implementation "org.jetbrains.anko:anko-commons:$anko_version"
+
+ implementation group: 'io.ktor', name: 'ktor-client-android', version: '1.2.5'
+ implementation("io.ktor:ktor-client-android:1.2.5")
}
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml b/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml
index 59f36d365..c0a1c3165 100644
--- a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml
+++ b/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml
@@ -2,9 +2,11 @@
package="net.mamoe.mirai.demo">
+
+
@@ -18,5 +20,6 @@
+
diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt
new file mode 100644
index 000000000..7bd0bec4c
--- /dev/null
+++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt
@@ -0,0 +1,11 @@
+package net.mamoe.mirai.demo
+
+import android.graphics.Bitmap
+
+interface LoginCallback {
+
+ suspend fun onCaptcha(bitmap: Bitmap)
+ suspend fun onSuccess()
+ suspend fun onFailed()
+ suspend fun onMessage(message:String)
+}
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt
new file mode 100644
index 000000000..fb28fe9d5
--- /dev/null
+++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt
@@ -0,0 +1,121 @@
+package net.mamoe.mirai.demo
+
+import android.app.ProgressDialog
+import android.app.Service
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Bundle
+import android.os.IBinder
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import io.ktor.util.cio.writeChannel
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.coroutines.*
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.event.subscribeFriendMessages
+import net.mamoe.mirai.login
+import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess
+import net.mamoe.mirai.utils.DefaultLogger
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.PlatformLogger
+import net.mamoe.mirai.utils.SimpleLogger
+import java.io.File
+
+class MainActivity : AppCompatActivity(),LoginCallback {
+
+
+ private lateinit var progressDialog : ProgressDialog
+
+ override suspend fun onCaptcha(bitmap: Bitmap) {
+ withContext(Dispatchers.Main){
+ ll_captcha.visibility = View.VISIBLE
+ iv_captcha.setImageBitmap(bitmap)
+ needCaptcha = true
+ if (progressDialog.isShowing){
+ progressDialog.dismiss()
+ }
+ }
+ }
+
+ override suspend fun onMessage(message:String) {
+ withContext(Dispatchers.Main){
+ msg.text = "${msg.text}\n$message"
+ }
+ }
+
+ override suspend fun onSuccess() {
+ withContext(Dispatchers.Main){
+ Toast.makeText(this@MainActivity,"登录成功",Toast.LENGTH_SHORT).show()
+ if (progressDialog.isShowing){
+ progressDialog.dismiss()
+ }
+ ll_captcha.visibility = View.GONE
+ et_pwd.visibility = View.GONE
+ et_qq.visibility = View.GONE
+ bt_login.visibility = View.GONE
+ }
+
+ }
+
+ override suspend fun onFailed() {
+ withContext(Dispatchers.Main){
+ Toast.makeText(this@MainActivity,"登录失败",Toast.LENGTH_SHORT).show()
+ if (progressDialog.isShowing){
+ progressDialog.dismiss()
+ }
+ }
+ }
+
+ var binder: MiraiService.MiraiBinder? = null
+
+ var needCaptcha = false
+
+
+ private val conn = object : ServiceConnection {
+
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ binder = service as MiraiService.MiraiBinder?
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ binder = null
+ }
+
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ val intent = Intent(this, MiraiService::class.java)
+ startService(intent)
+ bindService(intent, conn, Service.BIND_AUTO_CREATE)
+ progressDialog = ProgressDialog(this)
+ bt_login.setOnClickListener {
+ if (!progressDialog.isShowing){
+ progressDialog.show()
+ }
+ binder?.setCallback(this)
+ if (!needCaptcha){
+ val qq = et_qq.text.toString().toUInt()
+ val pwd = et_pwd.text.toString()
+ binder?.startLogin(qq, pwd)
+ }else{
+ val captcha = et_captcha.text.toString()
+ binder?.setCaptcha(captcha)
+ }
+
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ unbindService(conn)
+ }
+
+}
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt
new file mode 100644
index 000000000..40755d382
--- /dev/null
+++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt
@@ -0,0 +1,159 @@
+package net.mamoe.mirai.demo
+
+import android.app.Service
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.os.Binder
+import android.os.IBinder
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.event.subscribeMessages
+import net.mamoe.mirai.login
+import net.mamoe.mirai.message.Image
+import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage
+import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
+import java.lang.ref.WeakReference
+
+class MiraiService : Service() {
+
+ private var mCaptchaDeferred: CompletableDeferred? = null
+
+ private var mBot: Bot? = null
+
+ private var mCaptcha = ""
+ set(value) {
+ field = value
+ mCaptchaDeferred?.complete(value)
+ }
+
+ private var mBinder: MiraiBinder? = null
+
+ private var mCallback: WeakReference? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ mBinder = MiraiBinder()
+
+ }
+
+ private fun login(qq: UInt, pwd: String) {
+ GlobalScope.launch {
+ mBot = Bot(qq, pwd).apply {
+ val loginResult = login {
+ captchaSolver = {
+ val byteArray = byteArrayOf()
+ it.readFully(byteArray, 0, it.writeRemaining)
+ val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
+ mCallback?.get()?.onCaptcha(bitmap)
+ mCaptchaDeferred?.await()
+ }
+ }
+ if (loginResult == LoginResult.SUCCESS) {
+ mCallback?.get()?.onSuccess()
+ } else {
+ mCallback?.get()?.onFailed()
+ }
+ }
+
+
+ mBot!!.subscribeMessages {
+ content({ true }) {
+ mCallback?.get()?.onMessage("收到来自${sender.id}的消息")
+ }
+
+ // 当接收到消息 == "你好" 时就回复 "你好!"
+ "你好" reply "你好!"
+
+ // 当消息 == "查看 subject" 时, 执行 lambda
+ case("查看 subject") {
+ if (subject is QQ) {
+ reply("消息主体为 QQ, 你在跟发私聊消息")
+ } else {
+ reply("消息主体为 Group, 你在群里发消息")
+ }
+
+ // 在回复的时候, 一般使用 subject 来作为回复对象.
+ // 因为当群消息时, subject 为这个群.
+ // 当好友消息时, subject 为这个好友.
+ // 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标
+ }
+
+
+ // 当消息里面包含这个类型的消息时
+ has {
+ // this: MessagePacket
+ // message: MessageChain
+ // sender: QQ
+ // it: String (MessageChain.toString)
+
+ if (this is GroupMessage) {
+ //如果是群消息
+ // group: Group
+ this.group.sendMessage("你在一个群里")
+ // 等同于 reply("你在一个群里")
+ }
+
+ reply("图片, ID= ${message[Image].id}")//获取第一个 Image 类型的消息
+ reply(message)
+ }
+
+
+ "123" containsReply "你的消息里面包含 123"
+
+
+ // 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String
+ "我的qq" reply { sender.id.toString() }
+
+
+ // 当消息前缀为 "我是" 时
+ startsWith("我是", removePrefix = true) {
+ // it: 删除了消息前缀 "我是" 后的消息
+ // 如一条消息为 "我是张三", 则此时的 it 为 "张三".
+
+ reply("你是$it")
+ }
+
+
+ // 当消息中包含 "复读" 时
+ contains("复读") {
+ reply(message)
+ }
+
+
+ // 自定义的 filter, filter 中 it 为转为 String 的消息.
+ // 也可以用任何能在处理时使用的变量, 如 subject, sender, message
+ content({ it.length == 3 }) {
+ reply("你发送了长度为 3 的消息")
+ }
+
+ }
+ }
+
+ }
+
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return mBinder
+ }
+
+
+ inner class MiraiBinder : Binder() {
+
+ fun startLogin(qq: UInt, pwd: String) {
+ login(qq, pwd)
+ }
+
+ fun setCaptcha(captcha: String) {
+ mCaptcha = captcha
+ }
+
+ fun setCallback(callback: LoginCallback) {
+ mCallback = WeakReference(callback)
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt
deleted file mode 100644
index 4d438758c..000000000
--- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
-
-package net.mamoe.mirai.demo
-
-import android.app.Application
-import android.os.Bundle
-import android.widget.LinearLayout
-import androidx.appcompat.app.AppCompatActivity
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.subscribeFriendMessages
-import net.mamoe.mirai.login
-import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess
-import net.mamoe.mirai.utils.DefaultLogger
-import net.mamoe.mirai.utils.PlatformLogger
-import net.mamoe.mirai.utils.SimpleLogger
-import java.io.ByteArrayOutputStream
-import java.io.PrintStream
-import kotlin.properties.Delegates
-
-@Suppress("unused")
-private val Throwable.stacktraceString: String
- get() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString()
-
-class MyApplication : Application()
-
-class MainActivity : AppCompatActivity() {
- private var rootLayout: LinearLayout by Delegates.notNull()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- rootLayout = findViewById(R.id.main_view)
-
-
- }
-
- private suspend fun initializeBot(qq: UInt, password: String) {
- DefaultLogger = {
- PlatformLogger(it) + SimpleLogger { message, e ->
- // TODO: 2019/11/6
- }
- }
-
- val bot = Bot(qq, password).apply {
- login {
- captchaSolver = {
-
- "ABCD"
- }
- }.requireSuccess()
- }
-
- bot.subscribeFriendMessages {
- "Hello" reply "Hello Mirai!"
- }
- }
-}
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml b/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml
index 520f84b9e..ce8b8004b 100644
--- a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml
+++ b/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml
@@ -1,8 +1,62 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml b/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml
new file mode 100644
index 000000000..3b2ef2eb7
--- /dev/null
+++ b/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Mirai
+
\ No newline at end of file