diff --git a/android/app/build.gradle b/android/app/build.gradle index d6c52fc..d6c01db 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,14 +5,15 @@ plugins { } android { + compileSdk 31 defaultConfig { applicationId "com.pushdeer.os" minSdk 22 targetSdk 31 - versionCode 5 - versionName "1.0-dev-5" + versionCode 8 + versionName "1.0-dev-8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -111,5 +112,7 @@ dependencies { implementation 'com.github.vishalkumarsinghvi:sign-in-with-apple-button-android:0.6' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' + api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.0' + +// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' } \ No newline at end of file diff --git a/android/app/debug/PushDeer-v1.0-dev-8.apk b/android/app/debug/PushDeer-v1.0-dev-8.apk new file mode 100644 index 0000000..36a7633 Binary files /dev/null and b/android/app/debug/PushDeer-v1.0-dev-8.apk differ diff --git a/android/app/debug/output-metadata.json b/android/app/debug/output-metadata.json new file mode 100644 index 0000000..a458e4d --- /dev/null +++ b/android/app/debug/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.pushdeer.os", + "variantName": "debug", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 8, + "versionName": "1.0-dev-8", + "outputFile": "app-debug.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index a7dd0e8..c37c82e 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -27,4 +27,18 @@ # MiPush -keep class com.pushdeer.os.receiver.MessageReceiver {*;} #可以防止一个误报的 warning 导致无法成功编译,如果编译使用的 Android 版本是 23。 --dontwarn com.xiaomi.push.** \ No newline at end of file +-dontwarn com.xiaomi.push.** + + +# XiaoErMei +-keep class com.tencent.mm.opensdk.** { + *; +} + +-keep class com.tencent.wxop.** { + *; +} + +-keep class com.tencent.mm.sdk.** { + *; +} \ No newline at end of file diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk new file mode 100644 index 0000000..c9f9d20 Binary files /dev/null and b/android/app/release/app-release.apk differ diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json new file mode 100644 index 0000000..5432871 --- /dev/null +++ b/android/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.pushdeer.os", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 5, + "versionName": "1.0-dev-5", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9bf459d..fa431b3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -55,6 +55,15 @@ android:theme="@style/Theme.PushDeer.NoActionBar" /> + + + override lateinit var requestPermissionOpener: ActivityResultLauncher> + val wxRegReceiver: BroadcastReceiver by lazy { + object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + intent?.let { + when (it.action) { + ConstantsAPI.ACTION_REFRESH_WXAPP -> { + weChatLogin.iwxapi.registerApp(AppKeys.WX_Id) + } + WXEntryActivity.ACTION_RETURN_CODE -> { + val code = intent.getStringExtra(WXEntryActivity.CODE_KEY)!! + lifecycleScope.launch { + if (pushDeerViewModel.userInfo.isAppleLogin) { + Log.d("WH_", "onReceive: isAppleLogin") + // if login, perform merge + coroutineScope.launch { + pushDeerViewModel.userMerge( + "wechat", + code + ) { + coroutineScope.launch { + pushDeerViewModel.userInfo() + } + } + } + } else { + Log.d("WH_", "onReceive: plainLogin") + // if not, plain login + coroutineScope.launch { + pushDeerViewModel.loginWithWeiXin(code) { + globalNavController.navigate("main") { + globalNavController.popBackStack() + } + } + } + } + } + } + else -> { + } + } + } + + } + } + } + @ExperimentalAnimationApi @ExperimentalMaterialApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + registerReceiver(wxRegReceiver, + IntentFilter().apply { + addAction(ConstantsAPI.ACTION_REFRESH_WXAPP) + addAction(WXEntryActivity.ACTION_RETURN_CODE) + }) + + NotificationUtil.setupChannel(this) myActivity = this @@ -124,7 +185,7 @@ class MainActivity : AppCompatActivity(), RequestHolder { Color.Transparent, useDarkIcons ) - else -> systemUiController.setSystemBarsColor(Color.Transparent, useDarkIcons) + else -> systemUiController.setSystemBarsColor(Color.Transparent, !useDarkIcons) } WindowCompat.setDecorFitsSystemWindows(window, true) miPushRepository.regId.observe(this) { @@ -133,7 +194,7 @@ class MainActivity : AppCompatActivity(), RequestHolder { SideEffect { coroutineScope.launch { - pushDeerViewModel.login(onReturn = { + pushDeerViewModel.loginWithApple(onReturn = { globalNavController.navigate("main") { globalNavController.popBackStack() } @@ -165,4 +226,9 @@ class MainActivity : AppCompatActivity(), RequestHolder { } } } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(wxRegReceiver) + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/data/api/PushDeerApi.kt b/android/app/src/main/java/com/pushdeer/os/data/api/PushDeerApi.kt index c02b721..60f2097 100644 --- a/android/app/src/main/java/com/pushdeer/os/data/api/PushDeerApi.kt +++ b/android/app/src/main/java/com/pushdeer/os/data/api/PushDeerApi.kt @@ -13,7 +13,19 @@ interface PushDeerApi { @FormUrlEncoded @POST("/login/idtoken") - suspend fun loginIdToken(@Field("idToken") idToken: String): ReturnData + suspend fun loginWithAppleIdToken(@Field("idToken") idToken: String): ReturnData + + @FormUrlEncoded + @POST("/login/wecode") + suspend fun loginWithWeXin(@Field("code") code: String): ReturnData + + @FormUrlEncoded + @POST("/user/merge") + suspend fun userMerge( + @Field("token") token: String, + @Field("type") type: String, // apple wechat + @Field("tokenorcode") tokenorcode: String // input idToken / code + ): String // @GET("/login/fake") // suspend fun fakeLogin(): ReturnData diff --git a/android/app/src/main/java/com/pushdeer/os/data/api/data/response/UserInfo.kt b/android/app/src/main/java/com/pushdeer/os/data/api/data/response/UserInfo.kt index 1358c4e..1fca47b 100644 --- a/android/app/src/main/java/com/pushdeer/os/data/api/data/response/UserInfo.kt +++ b/android/app/src/main/java/com/pushdeer/os/data/api/data/response/UserInfo.kt @@ -2,22 +2,44 @@ package com.pushdeer.os.data.api.data.response class UserInfo { var id: String = "" +// var uid: String = "" var name: String = "" var email: String = "" - var app_id: String = "" - var wechat_id: String = "" + var apple_id: String? = "" + var wechat_id: String? = "" var level: Int = 1 var created_at: String = "" var updated_at: String = "" override fun toString(): String { return "id:$id\n" + +// "uid:$uid\n" + "name:$name\n" + "email:$email\n" + - "app_id:$app_id\n" + + "apple_id:$apple_id\n" + "wechat_id:$wechat_id\n" + "level:$level\n" + "created:$created_at\n" + "updated:$updated_at" } + + val isWeChatLogin: Boolean + get() { + return if (wechat_id == null) { + false + } else { + wechat_id!!.length > 4 + } + } + val isAppleLogin: Boolean + get() { + return if (apple_id == null) { + false + } else { + apple_id!!.length > 4 + } + } + + val isLogin: Boolean + get() = isWeChatLogin or isAppleLogin } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/data/database/dao/LogDogDao.kt b/android/app/src/main/java/com/pushdeer/os/data/database/dao/LogDogDao.kt index da0ae3a..f2ac3de 100644 --- a/android/app/src/main/java/com/pushdeer/os/data/database/dao/LogDogDao.kt +++ b/android/app/src/main/java/com/pushdeer/os/data/database/dao/LogDogDao.kt @@ -15,6 +15,9 @@ interface LogDogDao { @Insert suspend fun insert(vararg logDog: LogDog) + @Insert + fun insert1(vararg logDog: LogDog) + @Query("delete from LogDog") suspend fun clear() } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/data/database/entity/LogDog.kt b/android/app/src/main/java/com/pushdeer/os/data/database/entity/LogDog.kt index 960c90a..b7bbc5c 100644 --- a/android/app/src/main/java/com/pushdeer/os/data/database/entity/LogDog.kt +++ b/android/app/src/main/java/com/pushdeer/os/data/database/entity/LogDog.kt @@ -18,6 +18,7 @@ class LogDog { return "id:$id\n" + "level:$level\n" + "entity:$entity\n" + + "event:$event\n" + "log:$log\n" + "time:${timestamp.toTimestamp()}" } diff --git a/android/app/src/main/java/com/pushdeer/os/data/repository/LogDogRepository.kt b/android/app/src/main/java/com/pushdeer/os/data/repository/LogDogRepository.kt index c3492aa..799f455 100644 --- a/android/app/src/main/java/com/pushdeer/os/data/repository/LogDogRepository.kt +++ b/android/app/src/main/java/com/pushdeer/os/data/repository/LogDogRepository.kt @@ -2,13 +2,14 @@ package com.pushdeer.os.data.repository import com.pushdeer.os.data.database.dao.LogDogDao import com.pushdeer.os.data.database.entity.LogDog +import com.pushdeer.os.store.SettingStore import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -class LogDogRepository(private val logDogDao: LogDogDao) { +class LogDogRepository(private val logDogDao: LogDogDao,private val settingStore: SettingStore) { val all = logDogDao.all - suspend fun clear(){ + suspend fun clear() { logDogDao.clear() } @@ -28,6 +29,20 @@ class LogDogRepository(private val logDogDao: LogDogDao) { } } + fun log( + entity: String, + level: String, + event: String, + log: String + ) { + logDogDao.insert1(LogDog().apply { + this.entity = entity + this.level = level + this.event = event + this.log = log + }) + } + suspend fun logi(entity: String, event: String, log: String) { withContext(Dispatchers.IO) { insert(LogDog.logi(entity, event, log)) diff --git a/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt b/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt index 6f53489..1386e27 100644 --- a/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt +++ b/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt @@ -4,6 +4,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Intent import android.content.res.Resources +import android.util.Log import androidx.activity.result.ActivityResultLauncher import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity @@ -23,7 +24,12 @@ import com.pushdeer.os.viewmodel.LogDogViewModel import com.pushdeer.os.viewmodel.MessageViewModel import com.pushdeer.os.viewmodel.PushDeerViewModel import com.pushdeer.os.viewmodel.UiViewModel +import com.tencent.mm.opensdk.modelmsg.SendAuth +import com.tencent.mm.opensdk.openapi.IWXAPI import com.wh.common.activity.QrScanActivity +import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration +import com.willowtreeapps.signinwithapplebutton.SignInWithAppleResult +import com.willowtreeapps.signinwithapplebutton.SignInWithAppleService import io.noties.markwon.Markwon import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -40,16 +46,20 @@ interface RequestHolder { val myActivity: AppCompatActivity val markdown: Markwon val qrScanActivityOpener: ActivityResultLauncher - val requestPermissionOpener:ActivityResultLauncher> + val requestPermissionOpener: ActivityResultLauncher> val coilImageLoader: ImageLoader - val fragmentManager: FragmentManager + // requests val alert: AlertRequest - val key:KeyRequest - val device:DeviceRequest - val message:MessageRequest - val clip:ClipRequest + val key: KeyRequest + val device: DeviceRequest + val message: MessageRequest + val clip: ClipRequest + val weChatLogin: WeChatLoginRequest + val appleLogin: AppleLoginRequest + +// val iwxapi: IWXAPI fun startQrScanActivity() { qrScanActivityOpener.launch(QrScanActivity.forScanResultIntent(myActivity)) @@ -61,29 +71,98 @@ interface RequestHolder { } fun clearLogDog() { - alert.alert(R.string.global_alert_title_confirm,"Clear?",onOk = { - logDogViewModel.clear() + alert.alert(R.string.global_alert_title_confirm, "Clear?", onOk = { + logDogViewModel.clear() }) } - fun userRename(newName:String){ + abstract class AppleLoginRequest( + private val fragmentManager: FragmentManager, + private val requestHolder: RequestHolder + ) { + private val appleLoginCallBack: (SignInWithAppleResult) -> Unit = + { result: SignInWithAppleResult -> + when (result) { + is SignInWithAppleResult.Success -> { + if (requestHolder.pushDeerViewModel.userInfo.isWeChatLogin) { + // if login with wechat, perform merge + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.userMerge( + type = "apple", + tokenorcode = result.idToken, + onReturn = { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.userInfo() + } + } + ) + } + } else { + // else ( is not login ), plain login with apple + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.loginWithApple(result.idToken) { + requestHolder.globalNavController.navigate("main") { + requestHolder.globalNavController.popBackStack() + } + } + } + } + } + is SignInWithAppleResult.Failure -> { + requestHolder.alert.alert("Warning Apple Id Login Failed", { + result.error.message + }, onOk = {}) + Log.d( + "WH_", + "Received error from Apple Sign In ${result.error.message}" + ) + } + is SignInWithAppleResult.Cancel -> { + Log.d("WH_", "User canceled Apple Sign In") + } + } + } + private val appleLoginConfiguration = SignInWithAppleConfiguration.Builder() + .clientId("com.pushdeer.site") + .redirectUri("https://api2.pushdeer.com/callback/apple") + .responseType(SignInWithAppleConfiguration.ResponseType.ALL) + .scope(SignInWithAppleConfiguration.Scope.EMAIL) + .build() + + val login = { + val service = SignInWithAppleService( + fragmentManager = fragmentManager, + fragmentTag = "SignInWithAppleButton-1-SignInWebViewDialogFragment", + configuration = appleLoginConfiguration, + callback = appleLoginCallBack + ) + service.show() + } } -// abstract class LogDogRequest(private val ) + abstract class WeChatLoginRequest(val iwxapi: IWXAPI) { + val login: () -> Unit = { + val req = SendAuth.Req() + req.scope = "snsapi_userinfo" + req.state = System.currentTimeMillis().toString() + iwxapi.sendReq(req) + } + } abstract class ClipRequest(private val clipboardManager: ClipboardManager) { fun copyMessagePlainText(str: String) { clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-copy-plain-text", str)) } - fun copyPushKey(str: String){ + fun copyPushKey(str: String) { clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-copy-pushkey", str)) } } abstract class AlertRequest(private val resources: Resources) { - val show: MutableState = mutableStateOf(false) + val show2BtnDialog: MutableState = mutableStateOf(false) + val show1BtnDialog: MutableState = mutableStateOf(false) var title: String = "" var content: @Composable () -> Unit = {} var onOKAction: () -> Unit = {} @@ -94,19 +173,34 @@ interface RequestHolder { title: String, content: @Composable () -> Unit, onOk: () -> Unit, - onCancel: () -> Unit = {} + ) { + this.title = title + this.content = content + this.onOKAction = onOk + this.show1BtnDialog.value = true + } + + fun alert( + title: String, + content: @Composable () -> Unit, + onOk: () -> Unit, + onCancel: () -> Unit ) { this.title = title this.content = content this.onOKAction = onOk this.onCancelAction = onCancel - this.show.value = true + this.show2BtnDialog.value = true } fun alert(title: String, content: String, onOk: () -> Unit, onCancel: () -> Unit = {}) { alert(title, { Text(text = content) }, onOk, onCancel) } + fun alert(title: String, content: String, onOk: () -> Unit) { + alert(title, { Text(text = content) }, onOk) + } + fun alert( @StringRes title: Int, @StringRes content: Int, @@ -116,6 +210,14 @@ interface RequestHolder { alert(resources.getString(title), resources.getString(content), onOk, onCancel) } + fun alert( + @StringRes title: Int, + @StringRes content: Int, + onOk: () -> Unit = {}, + ) { + alert(resources.getString(title), resources.getString(content), onOk) + } + fun alert( @StringRes title: Int, content: @Composable () -> Unit, @@ -125,6 +227,14 @@ interface RequestHolder { alert(resources.getString(title), content, onOk, onCancel) } + fun alert( + @StringRes title: Int, + content: @Composable () -> Unit, + onOk: () -> Unit, + ) { + alert(resources.getString(title), content, onOk) + } + fun alert( @StringRes title: Int, content: String, @@ -133,9 +243,17 @@ interface RequestHolder { ) { alert(resources.getString(title), content, onOk, onCancel) } + + fun alert( + @StringRes title: Int, + content: String, + onOk: () -> Unit, + ) { + alert(resources.getString(title), content, onOk) + } } - abstract class KeyRequest(private val requestHolder: RequestHolder){ + abstract class KeyRequest(private val requestHolder: RequestHolder) { fun gen() { requestHolder.coroutineScope.launch { requestHolder.pushDeerViewModel.keyGen() @@ -166,7 +284,7 @@ interface RequestHolder { } } - abstract class DeviceRequest(private val requestHolder: RequestHolder){ + abstract class DeviceRequest(private val requestHolder: RequestHolder) { fun deviceReg(deviceInfo: DeviceInfo) { requestHolder.coroutineScope.launch { requestHolder.pushDeerViewModel.deviceReg(deviceInfo) @@ -191,7 +309,7 @@ interface RequestHolder { } } - abstract class MessageRequest(private val requestHolder: RequestHolder){ + abstract class MessageRequest(private val requestHolder: RequestHolder) { fun messagePush(text: String, desp: String, type: String, pushkey: String) { requestHolder.coroutineScope.launch { requestHolder.pushDeerViewModel.messagePush(text, desp, type, pushkey) @@ -200,16 +318,21 @@ interface RequestHolder { fun messagePushTest(text: String) { if (requestHolder.pushDeerViewModel.keyList.isNotEmpty()) { - messagePush(text, "pushtest", "markdown", requestHolder.pushDeerViewModel.keyList[0].key) + messagePush( + text, + "pushtest", + "markdown", + requestHolder.pushDeerViewModel.keyList[0].key + ) requestHolder.coroutineScope.launch { - delay(1000) + delay(900) requestHolder.pushDeerViewModel.messageList() } } else { requestHolder.alert.alert( R.string.global_alert_title_alert, - R.string.main_message_send_alert, - onOk = {}) + R.string.main_message_send_alert + ) } } diff --git a/android/app/src/main/java/com/pushdeer/os/keeper/RepositoryKeeper.kt b/android/app/src/main/java/com/pushdeer/os/keeper/RepositoryKeeper.kt index cd01fd2..77cc712 100644 --- a/android/app/src/main/java/com/pushdeer/os/keeper/RepositoryKeeper.kt +++ b/android/app/src/main/java/com/pushdeer/os/keeper/RepositoryKeeper.kt @@ -4,9 +4,10 @@ import com.pushdeer.os.data.database.AppDatabase import com.pushdeer.os.data.repository.LogDogRepository import com.pushdeer.os.data.repository.MessageRepository import com.pushdeer.os.data.repository.MiPushRepository +import com.pushdeer.os.store.SettingStore -class RepositoryKeeper(database: AppDatabase) { +class RepositoryKeeper(database: AppDatabase,settingStore: SettingStore) { val miPushRepository = MiPushRepository() - val logDogRepository = LogDogRepository(database.logDogDao()) + val logDogRepository = LogDogRepository(database.logDogDao(),settingStore) val messageRepository = MessageRepository(database.messageDao()) } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/okhttp/LogInterceptor.kt b/android/app/src/main/java/com/pushdeer/os/okhttp/LogInterceptor.kt new file mode 100644 index 0000000..472b230 --- /dev/null +++ b/android/app/src/main/java/com/pushdeer/os/okhttp/LogInterceptor.kt @@ -0,0 +1,23 @@ +package com.pushdeer.os.okhttp + +import android.util.Log +import com.pushdeer.os.data.repository.LogDogRepository +import okhttp3.Interceptor +import okhttp3.Response + +class LogInterceptor(private val logDogRepository: LogDogRepository): Interceptor{ + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + val url = request.url + val methods = request.method + val isHttps = request.isHttps + val contentType = request.body?.contentType() + + val response = chain.proceed(request) + + Log.d("WH_", "intercept: $response.") + + return response + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/store/SettingStore.kt b/android/app/src/main/java/com/pushdeer/os/store/SettingStore.kt index 3bdc95d..387c474 100644 --- a/android/app/src/main/java/com/pushdeer/os/store/SettingStore.kt +++ b/android/app/src/main/java/com/pushdeer/os/store/SettingStore.kt @@ -18,4 +18,6 @@ class SettingStore(context:Context) { var showMessageSender by store.boolean("show-message-sender",true) var thisPushSdk by store.string("this-push-sdk","mi-push") var thisDeviceId by store.string("this-device-id","") + + var logLevel by store.string("log-level","i") // i w e - d } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt index 418ecf6..579d6e5 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt @@ -30,12 +30,12 @@ fun CardItemSingleLineWithIcon( ) { Card( onClick = onClick, - shape = RoundedCornerShape(4.dp), + shape = RoundedCornerShape(8.dp), modifier = Modifier .border( width = 1.dp, color = MainBlue, - shape = RoundedCornerShape(4.dp) + shape = RoundedCornerShape(8.dp) ), elevation = 5.dp @@ -72,13 +72,13 @@ fun CardItemMultiLine( ) { Card( onClick = onClick, - shape = RoundedCornerShape(4.dp), + shape = RoundedCornerShape(8.dp), modifier = Modifier // .padding(bottom = 16.dp) .border( width = 1.dp, color = MainBlue, - shape = RoundedCornerShape(4.dp) + shape = RoundedCornerShape(8.dp) ), elevation = 5.dp @@ -111,12 +111,12 @@ fun CardItemMultiLine( fun CardItemWithContent(onClick: () -> Unit, content: @Composable () -> Unit = {}) { Card( onClick = onClick, - shape = RoundedCornerShape(4.dp), + shape = RoundedCornerShape(8.dp), modifier = Modifier .border( width = 1.dp, color = MainBlue, - shape = RoundedCornerShape(4.dp) + shape = RoundedCornerShape(8.dp) ), content = content, elevation = 5.dp @@ -127,12 +127,12 @@ fun CardItemWithContent(onClick: () -> Unit, content: @Composable () -> Unit = { @Composable fun CardItemWithContent(content: @Composable () -> Unit = {}) { Card( - shape = RoundedCornerShape(4.dp), + shape = RoundedCornerShape(8.dp), modifier = Modifier .border( width = 1.dp, color = MainBlue, - shape = RoundedCornerShape(4.dp) + shape = RoundedCornerShape(8.dp) ), content = content, elevation = 5.dp diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt index d40b555..b3780b6 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt @@ -176,7 +176,7 @@ fun KeyItem(key: PushKey, requestHolder: RequestHolder) { backgroundColor = MaterialTheme.colors.MBlue, contentColor = Color.White ), - shape = RoundedCornerShape(6.dp) + shape = RoundedCornerShape(8.dp) ) { Text(text = stringResource(id = R.string.main_key_copy)) } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt index c8ca6a2..1a0407b 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt @@ -33,7 +33,7 @@ fun PlainTextMessageItem(message: MessageEntity,requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) + .clip(RoundedCornerShape(8.dp)) .clickable { requestHolder.clip.copyMessagePlainText(message.text) } @@ -80,7 +80,7 @@ fun ImageMessageItem(message: MessageEntity, requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) + .clip(RoundedCornerShape(8.dp)) .clickable { requestHolder.clip.copyMessagePlainText(message.text) } @@ -125,7 +125,7 @@ fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) + .clip(RoundedCornerShape(8.dp)) .clickable { requestHolder.clip.copyMessagePlainText(message.text) } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MyAlertDialog.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MyAlertDialog.kt index 8a428d4..79865c8 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MyAlertDialog.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MyAlertDialog.kt @@ -10,13 +10,13 @@ import com.pushdeer.os.holder.RequestHolder @Composable fun MyAlertDialog(alertRequest: RequestHolder.AlertRequest) { - if (alertRequest.show.value) { + if (alertRequest.show2BtnDialog.value) { AlertDialog( - onDismissRequest = { alertRequest.show.value = false }, + onDismissRequest = { alertRequest.show2BtnDialog.value = false }, confirmButton = { TextButton(onClick = { alertRequest.onOKAction.invoke() - alertRequest.show.value = false + alertRequest.show2BtnDialog.value = false }) { Text(text = stringResource(id = R.string.global_alert_ok)) } @@ -25,7 +25,7 @@ fun MyAlertDialog(alertRequest: RequestHolder.AlertRequest) { dismissButton = { TextButton(onClick = { alertRequest.onCancelAction.invoke() - alertRequest.show.value = false + alertRequest.show2BtnDialog.value = false }) { Text(text = stringResource(id = R.string.global_alert_cancel)) } @@ -35,4 +35,20 @@ fun MyAlertDialog(alertRequest: RequestHolder.AlertRequest) { text = alertRequest.content ) } + if (alertRequest.show1BtnDialog.value){ + AlertDialog( + onDismissRequest = { alertRequest.show1BtnDialog.value = false }, + confirmButton = { + TextButton(onClick = { + alertRequest.onOKAction.invoke() + alertRequest.show1BtnDialog.value = false + }) { + Text(text = stringResource(id = R.string.global_alert_ok)) + } + + }, + title = { Text(text = alertRequest.title) }, + text = alertRequest.content + ) + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SettingItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SettingItem.kt index 3535f9f..bfde477 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SettingItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SettingItem.kt @@ -14,11 +14,17 @@ import com.pushdeer.os.ui.theme.MBlue @ExperimentalMaterialApi @Composable -fun SettingItem(text: String, buttonString: String, onItemClick:()->Unit={},onButtonClick: () -> Unit) { +fun SettingItem( + text: String, + buttonString: String, + onItemClick: () -> Unit = {}, + paddingValues: PaddingValues = PaddingValues(bottom = 16.dp), + onButtonClick: () -> Unit +) { Column( modifier = Modifier .fillMaxWidth() - .padding(bottom = 16.dp) + .padding(paddingValues) ) { CardItemWithContent(onClick = onItemClick) { Row( @@ -51,4 +57,21 @@ fun SettingItem(text: String, buttonString: String, onItemClick:()->Unit={},onBu } } } -} \ No newline at end of file +} + +//@ExperimentalMaterialApi +//@Composable +//fun SettingItem(content: @Composable () -> Unit) { +// Card( +// onClick = onClick, +// shape = RoundedCornerShape(4.dp), +// modifier = Modifier +// .border( +// width = 1.dp, +// color = MainBlue, +// shape = RoundedCornerShape(4.dp) +// ), +// content = content, +// elevation = 5.dp +// ) +//} \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SwipeToDismissItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SwipeToDismissItem.kt index 7145826..4f23325 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SwipeToDismissItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/SwipeToDismissItem.kt @@ -1,82 +1,110 @@ package com.pushdeer.os.ui.compose.componment -import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.pushdeer.os.holder.RequestHolder +import com.pushdeer.os.ui.theme.SwipeToDismissGray +import com.pushdeer.os.ui.theme.SwipeToDismissRed import com.pushdeer.os.values.ConstValues +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @ExperimentalMaterialApi @Composable fun SwipeToDismissItem( onAction: () -> Unit, sidePadding: Boolean = false, + requestHolder: RequestHolder, content: @Composable RowScope.() -> Unit ) { - val dismissState = rememberDismissState() - if (dismissState.isDismissed(DismissDirection.EndToStart)) { - onAction() + var visible by remember { + mutableStateOf(true) } - Column(modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp)) { - SwipeToDismiss( - state = dismissState, - background = { -// val direction = dismissState.dismissDirection ?: return@SwipeToDismiss + val dismissv = DismissValue.Default + val dismissState = rememberDismissState(initialValue = dismissv) + LaunchedEffect(Unit){ + if (dismissState.isDismissed(DismissDirection.EndToStart)){ + requestHolder.coroutineScope.launch { + dismissState.reset() + } + } + } + AnimatedVisibility( + visible = visible, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) { - val color by animateColorAsState( - when (dismissState.targetValue) { - DismissValue.DismissedToEnd -> Color.Green - DismissValue.DismissedToStart -> Color.Red - else -> Color.Gray - } - ) + SwipeToDismiss( + state = dismissState, + background = { -// val alignment = when (direction) { -// DismissDirection.StartToEnd -> Alignment.CenterStart -// DismissDirection.EndToStart -> Alignment.CenterEnd -// } -// -// val icon = when (direction) { -// DismissDirection.StartToEnd -> Icons.Default.Done -// DismissDirection.EndToStart -> Icons.Default.Delete -// } - - val alignment = Alignment.CenterEnd - val icon = Icons.Default.Delete - - Box( - contentAlignment = alignment, - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(4.dp)) - .background(color) - .padding(end = 32.dp) - ) { - Icon( - imageVector = icon, - contentDescription = "", -// tint = Color.Red + val color by animateColorAsState( + when (dismissState.targetValue) { + DismissValue.DismissedToEnd -> SwipeToDismissGray + DismissValue.DismissedToStart -> SwipeToDismissRed + else -> SwipeToDismissGray + } ) - } - }, - directions = setOf(DismissDirection.EndToStart), - dismissThresholds = { direction -> - FractionalThreshold(if (direction == DismissDirection.EndToStart) 0.45f else 0.57f) - }, - dismissContent = content, - modifier = Modifier.padding(horizontal = if (sidePadding) ConstValues.MainPageSidePadding else 0.dp) - ) + + Row( + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + .background(color) + .padding(end = 32.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = { + requestHolder.coroutineScope.launch { + dismissState.reset() + } + }) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "", + ) + } + IconButton(onClick = { + visible = false + + requestHolder.coroutineScope.launch { + delay(300) + onAction() + } + }) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "", + ) + } + } + }, + directions = setOf(DismissDirection.EndToStart), + dismissThresholds = { + FractionalThreshold(0.45f) + }, + dismissContent = content, + modifier = Modifier.padding(horizontal = if (sidePadding) ConstValues.MainPageSidePadding else 0.dp) + ) + } + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/WeChatLoginButtom.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/WeChatLoginButtom.kt new file mode 100644 index 0000000..9928648 --- /dev/null +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/WeChatLoginButtom.kt @@ -0,0 +1,43 @@ +package com.pushdeer.os.ui.compose.componment + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.willowtreeapps.signinwithapplebutton.view.SignInWithAppleButton + +@Composable +fun WeChatLoginButton() { + Row( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .border(BorderStroke(1.dp, color = Color.Green), shape = RoundedCornerShape(4.dp)) + .background(color = Color.Green) + .width((150).dp) + .height(38.dp) + ) { + Text(text = "aaa") + } +} + +@Preview(showBackground = true) +@Composable +fun W() { + Row { + WeChatLoginButton() + AndroidView(factory = { + SignInWithAppleButton(it) + }, modifier = Modifier.border(1.dp, color = Color.Black, shape = RoundedCornerShape(4.dp))) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt index cfb79d6..c08ef80 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt @@ -1,37 +1,31 @@ package com.pushdeer.os.ui.compose.page -import android.util.Log import androidx.compose.foundation.Image +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView import com.pushdeer.os.R import com.pushdeer.os.holder.RequestHolder -import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration -import com.willowtreeapps.signinwithapplebutton.SignInWithAppleResult -import com.willowtreeapps.signinwithapplebutton.view.SignInWithAppleButton -import kotlinx.coroutines.launch +import com.pushdeer.os.ui.theme.MainGreen @ExperimentalMaterialApi @Composable fun LoginPage(requestHolder: RequestHolder) { Box(modifier = Modifier.fillMaxSize()) { - - val configuration = SignInWithAppleConfiguration.Builder() - .clientId("com.pushdeer.site") - .redirectUri("https://api2.pushdeer.com/callback/apple") - .responseType(SignInWithAppleConfiguration.ResponseType.ALL) - .scope(SignInWithAppleConfiguration.Scope.EMAIL) - .build() - Image( painter = painterResource(R.drawable.logo_com_x2), contentDescription = "big push deer logo", @@ -40,43 +34,50 @@ fun LoginPage(requestHolder: RequestHolder) { .align(Alignment.TopCenter) .padding(top = 50.dp) ) - AndroidView( - factory = { - SignInWithAppleButton(it).apply { - setUpSignInWithAppleOnClick( - requestHolder.fragmentManager, - configuration - ) { result -> - when (result) { - is SignInWithAppleResult.Success -> { - Log.d("WH_", "apple-id_token:${result.idToken}") - requestHolder.coroutineScope.launch { - requestHolder.pushDeerViewModel.login(result.idToken) { - requestHolder.globalNavController.navigate("main") { - requestHolder.globalNavController.popBackStack() - } - } - } - } - is SignInWithAppleResult.Failure -> { - requestHolder.alert.alert("Warning Apple Id Login Failed", { - result.error.message - }, onOk = {}) - Log.d( - "WH_", - "Received error from Apple Sign In ${result.error.message}" - ) - } - is SignInWithAppleResult.Cancel -> { - Log.d("WH_", "User canceled Apple Sign In") - } - } - } - } - }, + Card( + onClick = requestHolder.weChatLogin.login, + shape = RoundedCornerShape(4.dp), modifier = Modifier + .padding(bottom = 220.dp) + .border( + width = 1.dp, + color = MainGreen, + shape = RoundedCornerShape(4.dp) + ) .align(alignment = Alignment.BottomCenter) - .padding(bottom = 100.dp) - ) + + ) { + Text( + text = "Sign in with WeChat", + color = MainGreen, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(vertical = 16.dp) + .fillMaxWidth(0.6F) + ) + } + + Card( + onClick = requestHolder.appleLogin.login, + shape = RoundedCornerShape(4.dp), + modifier = Modifier + .padding(bottom = 140.dp) + .border( + width = 1.dp, + color = Color.Black, + shape = RoundedCornerShape(4.dp) + ) + .align(alignment = Alignment.BottomCenter) + + ) { + Text( + text = "Sign in with Apple", + color = Color.Black, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(vertical = 16.dp) + .fillMaxWidth(0.6F) + ) + } } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt index 66ed18f..dbca9ac 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt @@ -72,12 +72,13 @@ fun DeviceListPage(requestHolder: RequestHolder) { val state = rememberLazyListState() LazyColumn(state = state) { items( - items = requestHolder.pushDeerViewModel.deviceList, + items = requestHolder.pushDeerViewModel.deviceList.sortedByDescending { it.id }, key = { item: DeviceInfo -> item.id }) { deviceInfo: DeviceInfo -> var name by remember { mutableStateOf(deviceInfo.name) } SwipeToDismissItem( + requestHolder = requestHolder, onAction = { requestHolder.device.deviceRemove(deviceInfo) } ) { CardItemSingleLineWithIcon( diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt index 3a04287..d346db7 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt @@ -49,9 +49,11 @@ fun KeyListPage(requestHolder: RequestHolder) { }else{ LazyColumn(modifier = Modifier.fillMaxWidth()) { items( - requestHolder.pushDeerViewModel.keyList, + requestHolder.pushDeerViewModel.keyList.sortedBy { it.id }, key = { item: PushKey -> item.id }) { pushKey: PushKey -> - SwipeToDismissItem(onAction = { requestHolder.key.remove(pushKey) } + SwipeToDismissItem( + requestHolder = requestHolder, + onAction = { requestHolder.key.remove(pushKey) } ) { KeyItem(key = pushKey, requestHolder = requestHolder) } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPageFrame.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPageFrame.kt index 40af90b..ac73188 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPageFrame.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPageFrame.kt @@ -24,6 +24,7 @@ fun MainPageFrame( titleStringId: Int, showSideIcon: Boolean = true, sideIcon: ImageVector = Icons.Default.Add, + iconModifier: Modifier = Modifier, onSideIconClick: () -> Unit = {}, sidePadding: Boolean = true, content: @Composable BoxScope.() -> Unit @@ -53,7 +54,8 @@ fun MainPageFrame( Icon( imageVector = sideIcon, contentDescription = "", - tint = Color.LightGray + tint = Color.LightGray, + modifier = iconModifier ) } // } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt index 9aa9e15..64b3e98 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt @@ -1,6 +1,7 @@ package com.pushdeer.os.ui.compose.page.main import androidx.compose.animation.* +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -10,9 +11,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -37,9 +38,14 @@ fun MessageListPage(requestHolder: RequestHolder) { } } +// val a = rotat + + val a by animateFloatAsState(targetValue = if (requestHolder.uiViewModel.showMessageSender) 0F else 180F) + MainPageFrame( titleStringId = Page.Messages.labelStringId, - sideIcon = if (requestHolder.uiViewModel.showMessageSender) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + sideIcon = Icons.Default.KeyboardArrowDown, + iconModifier = Modifier.rotate(a), onSideIconClick = { requestHolder.toggleMessageSender() }, sidePadding = false ) { @@ -58,7 +64,7 @@ fun MessageListPage(requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() - .padding(bottom = 16.dp) + .padding(bottom = 20.dp) .padding(horizontal = ConstValues.MainPageSidePadding) ) { @@ -97,6 +103,7 @@ fun MessageListPage(requestHolder: RequestHolder) { items = messageList, key = { item: MessageEntity -> item.id }) { message: MessageEntity -> SwipeToDismissItem( + requestHolder = requestHolder, onAction = { requestHolder.message.messageRemove(message.toMessage(), onDone = { requestHolder.messageViewModel.delete(message) diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt index 4aba2bc..427b6ef 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt @@ -3,16 +3,32 @@ package com.pushdeer.os.ui.compose.page.main import android.content.Intent import android.net.Uri import android.util.Log -import androidx.compose.foundation.layout.fillMaxSize +import androidx.annotation.DrawableRes +import androidx.compose.animation.* +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.runtime.Composable +import androidx.compose.material.Icon +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.pushdeer.os.R +import com.pushdeer.os.data.api.data.response.UserInfo import com.pushdeer.os.holder.RequestHolder import com.pushdeer.os.ui.compose.componment.SettingItem import com.pushdeer.os.ui.navigation.Page +import com.pushdeer.os.ui.theme.MainBlue +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @ExperimentalMaterialApi @Composable @@ -21,51 +37,123 @@ fun SettingPage(requestHolder: RequestHolder) { titleStringId = Page.Settings.labelStringId, showSideIcon = false ) { + var showLoginMethod by remember { + mutableStateOf(false) + } + + LaunchedEffect(Unit) { + requestHolder.coroutineScope.launch { + delay(300) + showLoginMethod = true + } + } + LazyColumn( modifier = Modifier.fillMaxSize() ) { item { -// var newName by remember { -// mutableStateOf(requestHolder.pushDeerViewModel.userInfo.name) -// } - SettingItem( - text = "${stringResource(id = R.string.main_setting_user_hi)} ${requestHolder.pushDeerViewModel.userInfo.name} !", - buttonString = stringResource(id = R.string.main_setting_user_logout), - onItemClick = { - -// requestHolder.alert.alert( -// title = "修改用户名", -// content = { -// TextField( -// value = newName, -// onValueChange = { newName = it }, -// colors = TextFieldDefaults.textFieldColors( -// focusedIndicatorColor = Color.Transparent, -// unfocusedIndicatorColor = Color.Transparent, -// disabledIndicatorColor = Color.Transparent, -// errorIndicatorColor = Color.Transparent, -// ) -// ) -// }, -// onOk = { -// -// } -// ) - } + Card( + elevation = 5.dp, + modifier = Modifier.padding(bottom = 16.dp), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, MainBlue) ) { - requestHolder.pushDeerViewModel.deviceList.filter { it.device_id == requestHolder.settingStore.thisDeviceId } - .forEach { - requestHolder.device.deviceRemove(it) + Column( + modifier = Modifier + .fillMaxWidth() + ) { + SettingItem( + text = "${stringResource(id = R.string.main_setting_user_hi)} ${requestHolder.pushDeerViewModel.userInfo.name} !", + buttonString = stringResource(id = R.string.main_setting_user_logout), + paddingValues = PaddingValues(bottom = 0.dp), + onItemClick = {} + ) { + requestHolder.pushDeerViewModel.deviceList + .filter { it.device_id == requestHolder.settingStore.thisDeviceId } + .forEach { requestHolder.device.deviceRemove(it) } + requestHolder.settingStore.userToken = "" + requestHolder.globalNavController.navigate("login") { + requestHolder.globalNavController.popBackStack() + requestHolder.pushDeerViewModel.userInfo = UserInfo() + } + requestHolder.alert.alert( + title = R.string.global_alert_title_alert, + content = R.string.main_setting_alert_logout, + onOk = {} + ) + } + AnimatedVisibility( + visible = showLoginMethod, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + + // LoginMethod Row + Row( + Modifier + .fillMaxWidth() + .padding(bottom = 6.dp, start = 6.dp, end = 6.dp, top = 8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + + // wx 登陆按钮 + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(0.5F) + .clickable { + if (requestHolder.pushDeerViewModel.userInfo.isWeChatLogin) { + requestHolder.alert.alert( + title = "Hey", + content = "你已经通过 微信账号 成功登陆咯!", + onOk = {}) + } else { + requestHolder.alert.alert( + title = "绑定或迁移由 微信账号 创建的账号", + content = "请注意,如果你将要登陆的 微信账号 已经在此登陆过,其设备列表将会与当前账号合并, PushKey 将会在合并时丢失。", + onOk = requestHolder.weChatLogin.login + ) + } + } + ) { + LoginMethod( + isLogin = requestHolder.pushDeerViewModel.userInfo.isWeChatLogin, + id = R.drawable.ic_wechat_colored + ) + + } + + // apple 登陆按钮 + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(0.5F) + .clickable { + if (requestHolder.pushDeerViewModel.userInfo.isAppleLogin) { + requestHolder.alert.alert( + title = "Hey", + content = "你已经通过 Apple Id 成功登陆咯!", + onOk = {}) + } else { + requestHolder.alert.alert( + title = "绑定或迁移由 Apple Id 创建的账号", + content = "请注意,如果你将要用来登陆 PushDeer 的 Apple Id 已经在此登陆过,其设备列表将会与当前账号合并, PushKey 将会在合并时丢失。", + onOk = requestHolder.appleLogin.login + ) + } + } + ) { + LoginMethod( + isLogin = requestHolder.pushDeerViewModel.userInfo.isAppleLogin, + id = R.drawable.ic_apple_colored + ) + } + } } - requestHolder.settingStore.userToken = "" - requestHolder.globalNavController.navigate("login") { - requestHolder.globalNavController.popBackStack() } - requestHolder.alert.alert( - R.string.global_alert_title_alert, - R.string.main_setting_alert_logout, - {} - ) } } // item { @@ -81,7 +169,7 @@ fun SettingPage(requestHolder: RequestHolder) { text = stringResource(id = R.string.main_setting_douyoulike), buttonString = stringResource(id = R.string.main_setting_btn_like) ) { - val uri = Uri.parse("market://details?id=" + "com.pushdeer.os") + val uri = Uri.parse("market://details?id=com.pushdeer.os") Intent(Intent.ACTION_VIEW, uri).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }.let { @@ -107,12 +195,43 @@ fun SettingPage(requestHolder: RequestHolder) { } } -//@Composable -//fun TogglePreferenceItem(label: String) { -// Row( -// verticalAlignment = Alignment.CenterVertically, -// modifier = Modifier.fillMaxSize() -// ) { -// Text(text = label) -// } -//} \ No newline at end of file +@Composable +fun IconYes() { + Icon( + painter = painterResource(id = R.drawable.ic_okok2), + contentDescription = "", + tint = MainBlue, + modifier = Modifier.size(20.dp) + ) +} + +@Composable +fun IconNo() { + Icon( + painter = painterResource(id = R.drawable.ic_okok2), + contentDescription = "", + tint = Color.Gray, + modifier = Modifier.size(20.dp) + ) +} + +@Composable +fun LoginMethod(isLogin: Boolean, @DrawableRes id: Int) { + Row( + modifier = Modifier.width(70.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + if (isLogin) + IconYes() + else + IconNo() + Image( + painter = painterResource(id = id), + contentDescription = "", + modifier = Modifier + .padding(vertical = 12.dp) + .size(35.dp), + ) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/theme/Color.kt b/android/app/src/main/java/com/pushdeer/os/ui/theme/Color.kt index b55e96a..60288b6 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/theme/Color.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/theme/Color.kt @@ -9,4 +9,6 @@ val Teal200 = Color(0xFF03DAC5) val MainBlue = Color(0xFF3B4789) val MainGreen = Color(0xFF296C05) -val MainBottomBtn = Color(0xFF8E8E8E) \ No newline at end of file +val MainBottomBtn = Color(0xFF8E8E8E) +val SwipeToDismissRed = Color(0x92FFF2F2) +val SwipeToDismissGray = Color(0x92B6B6B6) \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/viewmodel/PushDeerViewModel.kt b/android/app/src/main/java/com/pushdeer/os/viewmodel/PushDeerViewModel.kt index db6eb02..e2a13a6 100644 --- a/android/app/src/main/java/com/pushdeer/os/viewmodel/PushDeerViewModel.kt +++ b/android/app/src/main/java/com/pushdeer/os/viewmodel/PushDeerViewModel.kt @@ -30,25 +30,24 @@ class PushDeerViewModel( val keyList = mutableStateListOf() // var messageList = mutableStateListOf() - suspend fun login(idToken: String = "", onReturn: (String) -> Unit = {}) { + suspend fun loginWithApple(idToken: String = "", onReturn: (String) -> Unit = {}) { withContext(Dispatchers.IO) { if (token == "" && idToken != "") { try { - pushDeerService.loginIdToken(idToken).let { + pushDeerService.loginWithAppleIdToken(idToken).let { it.content?.let { tokenOnly -> settingStore.userToken = tokenOnly.token token = tokenOnly.token - Log.d(TAG, "login: $token") + Log.d(TAG, "loginWithApple: $token") withContext(Dispatchers.Main) { onReturn.invoke(token) } } } - logDogRepository.logi("login", "normally", "nothing happened") + logDogRepository.logi("loginWithApple", "withAppleId", "nothing happened") } catch (e: Exception) { - Log.d(TAG, "login: ${e.localizedMessage}") - logDogRepository.loge("login", "", e.toString()) - return@withContext + Log.d(TAG, "loginWithApple: ${e.localizedMessage}") + logDogRepository.loge("loginWithApple", "", e.toString()) } } else if (token == "" && idToken == "") { return@withContext @@ -60,10 +59,50 @@ class PushDeerViewModel( } } + suspend fun loginWithWeiXin(code:String,onReturn: (String) -> Unit){ + withContext(Dispatchers.IO){ + try { + pushDeerService.loginWithWeXin(code).let { + it.content?.let { tokenOnly -> + settingStore.userToken = tokenOnly.token + token = tokenOnly.token + Log.d(TAG, "loginWithWeiXin: $token") + withContext(Dispatchers.Main) { + onReturn.invoke(token) + } + } + } + logDogRepository.logi("loginWithWeiXin", "withWeiXin", "nothing happened") + }catch (e: Exception){ + Log.e(TAG, "loginWithWeiXin: ${e.localizedMessage}") + logDogRepository.loge("loginWithWeiXin", "", e.toString()) + } + } + } + + suspend fun userMerge(type:String,tokenorcode:String,onReturn: (String) -> Unit={}){ + Log.d("WH_", ": token:${token} type:${type} tokenorcode:${tokenorcode}") + + withContext(Dispatchers.IO){ + try { + pushDeerService.userMerge(token,type,tokenorcode).let { + Log.d(TAG, "userMerge: $it") + withContext(Dispatchers.Main){ + onReturn("") + } + } + }catch (e: Exception) { + Log.e(TAG, "userMerge:e ${e.localizedMessage}") + logDogRepository.loge("userMerge", "", e.toString()) + } + } + } + suspend fun userInfo(onOk: (UserInfo) -> Unit = {}, onFailed: () -> Unit = {}) { withContext(Dispatchers.IO) { try { pushDeerService.userInfo(token).let { + Log.d(TAG, "userInfo: ${it.content}") it.content?.let { ita -> userInfo = ita withContext(Dispatchers.Main) { diff --git a/android/app/src/main/java/com/pushdeer/os/wxapi/WXEntryActivity.kt b/android/app/src/main/java/com/pushdeer/os/wxapi/WXEntryActivity.kt new file mode 100644 index 0000000..2f80f6d --- /dev/null +++ b/android/app/src/main/java/com/pushdeer/os/wxapi/WXEntryActivity.kt @@ -0,0 +1,69 @@ +package com.pushdeer.os.wxapi + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import com.pushdeer.os.App +import com.tencent.mm.opensdk.modelbase.BaseReq +import com.tencent.mm.opensdk.modelbase.BaseResp +import com.tencent.mm.opensdk.modelmsg.SendAuth +import com.tencent.mm.opensdk.openapi.IWXAPI +import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler + + +class WXEntryActivity : Activity(), IWXAPIEventHandler { + + companion object { + const val CODE_KEY = "code" + const val ACTION_RETURN_CODE = "return-code" + } + + private val iwxapi: IWXAPI by lazy { (application as App).iwxapi } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + iwxapi.handleIntent(intent, this); + } + + // 微信发送的请求 + override fun onReq(p0: BaseReq?) { + + } + + // 发送到微信请求的响应结果 + override fun onResp(p0: BaseResp?) { + when (p0?.errCode) { + BaseResp.ErrCode.ERR_AUTH_DENIED, BaseResp.ErrCode.ERR_USER_CANCEL -> +// if (RETURN_MSG_TYPE_SHARE === resp.getType()) { +// Toast.makeText(this, "分享失败", Toast.LENGTH_SHORT).show() +// } else { + Toast.makeText(this, "登录失败", Toast.LENGTH_SHORT).show() +// } + BaseResp.ErrCode.ERR_OK -> when (p0.type) { + 1 -> { + // login + val code: String = (p0 as SendAuth.Resp).code + code.let { + sendBroadcast(Intent().apply { + putExtra(CODE_KEY, code) + action = ACTION_RETURN_CODE + }) + Log.d("WH_", "onResp: $code") + finish() + } + } +// RETURN_MSG_TYPE_LOGIN -> //拿到了微信返回的code,立马再去请求access_token +// var code +// : String +// ? +// = (resp as SendAuth.Resp).code +// RETURN_MSG_TYPE_SHARE -> { +// Toast.makeText(this, "微信分享成功", Toast.LENGTH_SHORT).show() +// finish() +// } + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/deer_placeholder.png b/android/app/src/main/res/drawable/deer_placeholder.png deleted file mode 100644 index ed7df69..0000000 Binary files a/android/app/src/main/res/drawable/deer_placeholder.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_apple_colored.xml b/android/app/src/main/res/drawable/ic_apple_colored.xml new file mode 100644 index 0000000..637d32a --- /dev/null +++ b/android/app/src/main/res/drawable/ic_apple_colored.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_markdown.xml b/android/app/src/main/res/drawable/ic_markdown.xml deleted file mode 100644 index 054618a..0000000 --- a/android/app/src/main/res/drawable/ic_markdown.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_okok2.xml b/android/app/src/main/res/drawable/ic_okok2.xml new file mode 100644 index 0000000..f54df64 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_okok2.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_wechat_colored.xml b/android/app/src/main/res/drawable/ic_wechat_colored.xml new file mode 100644 index 0000000..92b1bee --- /dev/null +++ b/android/app/src/main/res/drawable/ic_wechat_colored.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml index 5990b6a..745155c 100644 --- a/android/app/src/main/res/values-zh/strings.xml +++ b/android/app/src/main/res/values-zh/strings.xml @@ -29,4 +29,5 @@ 喜欢 PushDeer 吗? 喜欢 由于厂商推送设备服务限制,暂时不支持更换为自建 PushDeer 服务器,但仅更换登陆账号并不会影响您的使用。 + 确定删除? \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 06d7b27..b49cbc4 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -28,4 +28,5 @@ Do you line PushDeer? Like Due to the limitations of the vendor\'s push device service, it is not supported to change to the self-built PushDeer server for the time being, but only changing the login account will not affect your use. + Delete?