新增:纯客户端模式(启动APP时直接进入主动控制·客户端)

This commit is contained in:
pppscn 2022-06-08 14:38:12 +08:00
parent b210243a46
commit af898c7a3f
25 changed files with 166 additions and 503 deletions

View File

@ -100,7 +100,7 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" />
<activity
android:name=".activity.LoginActivity"
android:name=".activity.ClientActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true"
android:launchMode="singleInstance"

View File

@ -93,6 +93,12 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
override fun onCreate() {
super.onCreate()
try {
context = applicationContext
initLibs()
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//动态加载FrpcLib
val libPath = filesDir.absolutePath + "/libs"
val soFile = File(libPath)
@ -102,9 +108,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
Log.e("APP", throwable.message!!)
}
context = applicationContext
initLibs()
//启动前台服务
val intent = Intent(this, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@ -0,0 +1,14 @@
package com.idormy.sms.forwarder.activity
import android.os.Bundle
import androidx.viewbinding.ViewBinding
import com.idormy.sms.forwarder.core.BaseActivity
import com.idormy.sms.forwarder.fragment.ClientFragment
class ClientActivity : BaseActivity<ViewBinding?>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
openPage(ClientFragment::class.java)
}
}

View File

@ -1,28 +0,0 @@
package com.idormy.sms.forwarder.activity
import android.os.Bundle
import android.view.KeyEvent
import androidx.viewbinding.ViewBinding
import com.idormy.sms.forwarder.core.BaseActivity
import com.idormy.sms.forwarder.fragment.LoginFragment
import com.xuexiang.xui.utils.KeyboardUtils
import com.xuexiang.xui.utils.StatusBarUtils
import com.xuexiang.xutil.display.Colors
class LoginActivity : BaseActivity<ViewBinding?>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
openPage(LoginFragment::class.java, intent.extras)
}
override val isSupportSlideBack: Boolean
get() = false
override fun initStatusBarStyle() {
StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event)
}
}

View File

@ -6,6 +6,7 @@ import android.view.KeyEvent
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isFirstOpen
import com.xuexiang.xui.utils.KeyboardUtils
@ -44,29 +45,22 @@ class SplashActivity : BaseSplashActivity(), CancelAdapt {
}
if (isAgreePrivacy) {
loginOrGoMainPage()
whereToJump()
} else {
showPrivacyDialog(this) { dialog: MaterialDialog, _: DialogAction? ->
dialog.dismiss()
isAgreePrivacy = true
loginOrGoMainPage()
whereToJump()
}
}
}
private fun loginOrGoMainPage() {
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) {
val intent = Intent(App.context, if (hasToken()) MainActivity::class.java else LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
App.context.startActivity(intent)
private fun whereToJump() {
if (SettingUtils.enablePureClientMode) {
ActivityUtils.startActivity(ClientActivity::class.java)
} else {
if (hasToken()) {
ActivityUtils.startActivity(MainActivity::class.java)
} else {
ActivityUtils.startActivity(LoginActivity::class.java)
}
}*/
ActivityUtils.startActivity(MainActivity::class.java)
ActivityUtils.startActivity(MainActivity::class.java)
}
finish()
}

View File

@ -32,6 +32,7 @@ import com.xuexiang.xui.utils.DensityUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.WidgetUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.XUtil
@Suppress("PrivatePropertyName", "PropertyName")
@Page(name = "主动控制·客户端")
@ -69,7 +70,20 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(),
override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.menu_client)
//纯客户端模式
if (SettingUtils.enablePureClientMode) {
titleBar.setTitle(R.string.app_name).setSubTitle(getString(R.string.menu_client)).disableLeftView()
titleBar.addAction(object : TitleBar.ImageAction(R.drawable.ic_logout) {
@SingleClick
override fun performAction(view: View) {
XToastUtils.success(getString(R.string.exit_pure_client_mode))
SettingUtils.enablePureClientMode = false
XUtil.exitApp()
}
})
} else {
titleBar.setTitle(R.string.menu_client)
}
return titleBar
}

View File

@ -1,170 +0,0 @@
package com.idormy.sms.forwarder.fragment
import android.graphics.Color
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentLoginBinding
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.gotoProtocol
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog
import com.idormy.sms.forwarder.utils.RandomUtils.Companion.getRandomNumbersAndLetters
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy
import com.idormy.sms.forwarder.utils.TokenUtils.Companion.handleLoginSuccess
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.enums.CoreAnim
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.ThemeUtils
import com.xuexiang.xui.utils.ViewUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xutil.app.ActivityUtils
/**
* 登录页面
*
* @author xuexiang
* @since 2019-11-17 22:15
*/
@Page(anim = CoreAnim.none)
class LoginFragment : BaseFragment<FragmentLoginBinding?>(), View.OnClickListener {
private var mJumpView: View? = null
private var mCountDownHelper: CountDownButtonHelper? = null
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentLoginBinding {
return FragmentLoginBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()
titleBar?.run {
setImmersive(true)
setBackgroundColor(Color.TRANSPARENT)
setTitle("")
setLeftImageDrawable(ResUtils.getVectorDrawable(context, R.drawable.ic_login_close))
setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent))
mJumpView = addAction(object : TitleBar.TextAction(R.string.title_jump_login) {
override fun performAction(view: View) {
onLoginSuccess()
}
})
}
return titleBar
}
override fun initViews() {
mCountDownHelper = CountDownButtonHelper(binding!!.btnGetVerifyCode, 60)
//隐私政策弹窗
if (!isAgreePrivacy) {
showPrivacyDialog(requireContext()) { dialog: MaterialDialog, _: DialogAction? ->
dialog.dismiss()
handleSubmitPrivacy()
}
}
val isAgreePrivacy = isAgreePrivacy
binding!!.cbProtocol.isChecked = isAgreePrivacy
refreshButton(isAgreePrivacy)
binding!!.cbProtocol.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.isAgreePrivacy = isChecked
refreshButton(isChecked)
}
}
override fun initListeners() {
binding!!.btnGetVerifyCode.setOnClickListener(this)
binding!!.btnLogin.setOnClickListener(this)
binding!!.tvOtherLogin.setOnClickListener(this)
binding!!.tvForgetPassword.setOnClickListener(this)
binding!!.tvUserProtocol.setOnClickListener(this)
binding!!.tvPrivacyProtocol.setOnClickListener(this)
}
private fun refreshButton(isChecked: Boolean) {
ViewUtils.setEnabled(binding!!.btnLogin, isChecked)
ViewUtils.setEnabled(mJumpView, isChecked)
}
private fun handleSubmitPrivacy() {
isAgreePrivacy = true
UMengInit.init()
// 应用市场不让默认勾选
// ViewUtils.setChecked(cbProtocol, true);
}
@SingleClick
override fun onClick(v: View) {
val id = v.id
if (id == R.id.btn_get_verify_code) {
if (binding!!.etPhoneNumber.validate()) {
getVerifyCode(binding!!.etPhoneNumber.editValue)
}
} else if (id == R.id.btn_login) {
if (binding!!.etPhoneNumber.validate()) {
if (binding!!.etVerifyCode.validate()) {
loginByVerifyCode(
binding!!.etPhoneNumber.editValue,
binding!!.etVerifyCode.editValue
)
}
}
} else if (id == R.id.tv_other_login) {
XToastUtils.info("其他登录方式")
} else if (id == R.id.tv_forget_password) {
XToastUtils.info("忘记密码")
} else if (id == R.id.tv_user_protocol) {
gotoProtocol(this, isPrivacy = false, isImmersive = true)
} else if (id == R.id.tv_privacy_protocol) {
gotoProtocol(this, isPrivacy = true, isImmersive = true)
}
}
/**
* 获取验证码
*/
private fun getVerifyCode(phoneNumber: String) {
// TODO: 2020/8/29 这里只是界面演示而已
XToastUtils.warning("只是演示,验证码请随便输 phoneNumber=$phoneNumber")
mCountDownHelper!!.start()
}
/**
* 根据验证码登录
*
* @param phoneNumber 手机号
* @param verifyCode 验证码
*/
private fun loginByVerifyCode(phoneNumber: String, verifyCode: String) {
// TODO: 2020/8/29 这里只是界面演示而已
Log.d("loginByVerifyCode", "phoneNumber=$phoneNumber, verifyCode=$verifyCode")
onLoginSuccess()
}
/**
* 登录成功的处理
*/
private fun onLoginSuccess() {
val token = getRandomNumbersAndLetters(16)
if (handleLoginSuccess(token)) {
popToBack()
ActivityUtils.startActivity(MainActivity::class.java)
}
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
}

View File

@ -42,11 +42,13 @@ import com.xuexiang.xui.widget.picker.XRangeSlider
import com.xuexiang.xui.widget.picker.XRangeSlider.OnRangeSliderListener
import com.xuexiang.xui.widget.picker.XSeekBar
import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.XUtil.getPackageManager
import com.xuexiang.xutil.app.AppUtils.getAppPackageName
import com.xuexiang.xutil.data.DateUtils
import java.util.*
@Suppress("PropertyName", "SpellCheckingInspection")
@Page(name = "通用设置")
class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickListener {
@ -121,6 +123,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//帮助提示
switchHelpTip(binding!!.sbHelpTip)
//纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient)
}
override fun initListeners() {
@ -693,6 +698,24 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
}
//纯客户端模式
private fun switchDirectlyToClient(@SuppressLint("UseSwitchCompatOrMaterialCode") switchDirectlyToClient: SwitchButton) {
switchDirectlyToClient.isChecked = SettingUtils.enablePureClientMode
switchDirectlyToClient.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enablePureClientMode = isChecked
if (isChecked) {
MaterialDialog.Builder(requireContext())
.content(getString(R.string.enabling_pure_client_mode))
.positiveText(R.string.lab_yes)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
XUtil.exitApp()
}
.negativeText(R.string.lab_no)
.show()
}
}
}
//获取当前手机品牌
private fun getAutoStartTips(): String {
return when (Build.BRAND.lowercase(Locale.ROOT)) {

View File

@ -7,6 +7,7 @@ import android.os.Build
import android.util.Log
import com.idormy.sms.forwarder.activity.SplashActivity
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.SettingUtils
@Suppress("PropertyName")
class BootReceiver : BroadcastReceiver() {
@ -22,6 +23,9 @@ class BootReceiver : BroadcastReceiver() {
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//前台服务
val frontServiceIntent = Intent(context, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@ -30,6 +30,9 @@ class PhoneStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enablePhone) return

View File

@ -28,6 +28,9 @@ class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enableSms) return

View File

@ -31,8 +31,8 @@ class BatteryService : Service() {
super.onCreate()
Log.i(TAG, "onCreate--------------")
//是否同意隐私协议
//if (!MyApplication.allowPrivacyPolicy) return
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
val batteryFilter = IntentFilter()
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
@ -41,15 +41,16 @@ class BatteryService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand--------------")
return START_STICKY //
return START_STICKY
}
override fun onDestroy() {
Log.i(TAG, "onDestroy--------------")
super.onDestroy()
//是否同意隐私协议
//if (!MyApplication.allowPrivacyPolicy) return
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
unregisterReceiver(batteryReceiver)
}

View File

@ -72,6 +72,10 @@ class ForegroundService : Service() {
override fun onCreate() {
super.onCreate()
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
startForeground(FRONT_NOTIFY_ID, createForegroundNotification())
isRunning = true
@ -108,6 +112,12 @@ class ForegroundService : Service() {
}
override fun onDestroy() {
//纯客户端模式
if (SettingUtils.enablePureClientMode) {
super.onDestroy()
return
}
stopForeground(true)
compositeDisposable.dispose()
isRunning = false

View File

@ -6,6 +6,7 @@ import android.os.IBinder
import android.util.Log
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT
import com.idormy.sms.forwarder.utils.SettingUtils
import com.yanzhenjie.andserver.AndServer
import com.yanzhenjie.andserver.Server
import java.util.concurrent.TimeUnit
@ -28,6 +29,10 @@ class HttpService : Service(), Server.ServerListener {
override fun onCreate() {
super.onCreate()
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
Log.i(TAG, "onCreate: ")
server.startup()
}
@ -39,6 +44,10 @@ class HttpService : Service(), Server.ServerListener {
override fun onDestroy() {
super.onDestroy()
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
Log.i(TAG, "onDestroy: ")
server.shutdown()
}

View File

@ -40,6 +40,9 @@ class NotifyService : NotificationListenerService()/*, LifecycleOwner*/ {
}
override fun onListenerDisconnected() {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enableAppNotify) return
@ -51,6 +54,9 @@ class NotifyService : NotificationListenerService()/*, LifecycleOwner*/ {
override fun onNotificationPosted(sbn: StatusBarNotification?) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enableAppNotify) return

View File

@ -63,6 +63,7 @@ const val SP_ENABLE_SMS_TEMPLATE = "enable_sms_template"
const val SP_SMS_TEMPLATE = "sms_template"
const val SP_ENABLE_HELP_TIP = "enable_help_tip"
const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
const val CACTUS_TIMER = "cactus_timer"
const val CACTUS_LAST_TIMER = "cactus_last_timer"

View File

@ -268,6 +268,14 @@ class SettingUtils private constructor() {
set(enableHelpTip) {
MMKVUtils.put(SP_ENABLE_HELP_TIP, enableHelpTip)
}
//是否纯客户端模式
@JvmStatic
var enablePureClientMode: Boolean
get() = MMKVUtils.getBoolean(SP_PURE_CLIENT_MODE, false)
set(enablePureClientMode) {
MMKVUtils.put(SP_PURE_CLIENT_MODE, enablePureClientMode)
}
}
init {

View File

@ -1,85 +0,0 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import com.idormy.sms.forwarder.R
import com.umeng.analytics.MobclickAgent
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.app.ActivityUtils
import com.xuexiang.xutil.common.StringUtils
/**
* Token管理工具
*
* @author xuexiang
* @since 2019-11-17 22:37
*/
@Suppress("unused")
class TokenUtils private constructor() {
companion object {
private var sToken: String? = null
private const val KEY_TOKEN = "KEY_TOKEN"
private const val KEY_PROFILE_CHANNEL = "github"
/**
* 初始化Token信息
*/
@JvmStatic
fun init(context: Context) {
MMKVUtils.init(context)
sToken = MMKVUtils.getString(KEY_TOKEN, "")
}
private fun clearToken() {
sToken = null
MMKVUtils.remove(KEY_TOKEN)
}
var token: String?
get() = sToken
set(token) {
sToken = token
MMKVUtils.put(KEY_TOKEN, token)
}
@JvmStatic
fun hasToken(): Boolean {
return MMKVUtils.containsKey(KEY_TOKEN)
}
/**
* 处理登录成功的事件
*
* @param token 账户信息
*/
@JvmStatic
fun handleLoginSuccess(token: String?): Boolean {
return if (!StringUtils.isEmpty(token)) {
XToastUtils.success(ResUtils.getString(R.string.login_succeeded))
MobclickAgent.onProfileSignIn(KEY_PROFILE_CHANNEL, token)
Companion.token = token
true
} else {
XToastUtils.error(ResUtils.getString(R.string.login_failed))
false
}
}
/**
* 处理登出的事件
*/
@JvmStatic
fun handleLogoutSuccess() {
MobclickAgent.onProfileSignOff()
//登出时,清除账号信息
clearToken()
XToastUtils.success(ResUtils.getString(R.string.logout_succeeded))
SettingUtils.isAgreePrivacy = false
//跳转到登录页
ActivityUtils.startActivity(com.idormy.sms.forwarder.activity.LoginActivity::class.java)
}
}
init {
throw UnsupportedOperationException("u can't instantiate me...")
}
}

View File

@ -2,7 +2,6 @@ package com.idormy.sms.forwarder.utils.sdkinit
import android.app.Application
import com.idormy.sms.forwarder.core.BaseActivity
import com.idormy.sms.forwarder.utils.TokenUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xaop.XAOP
import com.xuexiang.xhttp2.XHttpSDK
@ -49,7 +48,6 @@ class XBasicLibInit private constructor() {
private fun initXUtil(application: Application) {
XUtil.init(application)
XUtil.debug(com.idormy.sms.forwarder.App.isDebug)
TokenUtils.init(application)
}
/**

View File

@ -17,7 +17,6 @@ import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.ResUtils.getString
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
@ -122,10 +121,10 @@ class WeworkAgentUtils private constructor() {
val resp = Gson().fromJson(response, DingtalkResult::class.java)
if (resp.errcode == 0L) {
SendUtils.updateLogs(logId, 2, response)
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
XToastUtils.success(getString(R.string.request_succeeded))
} else {
SendUtils.updateLogs(logId, 0, response)
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
XToastUtils.error(getString(R.string.request_failed) + response)
}
}

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/>
</vector>

View File

@ -1,191 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
style="@style/TextStyle.Title"
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:text="登 录"
android:textSize="30sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_phone" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:hint="@string/tip_please_input_phone_number"
android:inputType="number"
app:met_clearButton="true"
app:met_errorMessage="@string/tip_phone_number_error"
app:met_floatingLabel="normal"
app:met_floatingLabelText="@string/title_phone_number"
app:met_regexp="@string/regex_phone_number" />
</FrameLayout>
<FrameLayout
android:id="@+id/fl_verify_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_password" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_verify_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:hint="@string/hint_please_input_verify_code"
android:inputType="number"
app:met_clearButton="false"
app:met_errorMessage="@string/tip_verify_code_error"
app:met_floatingLabel="normal"
app:met_floatingLabelText="@string/lab_verify_code"
app:met_maxCharacters="4"
app:met_regexp="@string/regex_verify_code" />
<com.xuexiang.xui.widget.button.roundbutton.RoundButton
android:id="@+id/btn_get_verify_code"
style="@style/RoundButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingStart="10dp"
android:paddingTop="3dp"
android:paddingEnd="10dp"
android:paddingBottom="3dp"
android:text="@string/action_get_verify_code"
android:textColor="@color/selector_round_button_main_theme_color"
android:textSize="13sp"
app:rb_borderColor="@color/selector_round_button_main_theme_color"
app:rb_borderWidth="1.5dp"
app:rb_radius="15dp" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.xuexiang.xui.widget.alpha.XUIAlphaTextView
android:id="@+id/tv_other_login"
style="@style/TextStyle.Explain"
android:layout_gravity="start"
android:paddingStart="10dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:text="@string/other_login_methods"
android:textColor="@color/xui_config_color_primary_text" />
<com.xuexiang.xui.widget.alpha.XUIAlphaTextView
android:id="@+id/tv_forget_password"
style="@style/TextStyle.Explain"
android:layout_gravity="end"
android:paddingStart="10dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:text="@string/lab_forget_password"
android:textColor="@color/xui_config_color_primary_text" />
</FrameLayout>
</LinearLayout>
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_login"
style="@style/SuperButton.Primary.Login"
android:layout_marginTop="16dp"
android:text="@string/title_login_register" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="20dp"
android:gravity="center"
android:orientation="horizontal">
<CheckBox
android:id="@+id/cb_protocol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleX="0.7"
android:scaleY="0.7" />
<TextView
style="@style/TextStyle.Explain"
android:layout_width="wrap_content"
android:text="@string/agree_protocol"
android:textColor="@color/xui_config_color_primary_text" />
<com.xuexiang.xui.widget.alpha.XUIAlphaTextView
android:id="@+id/tv_user_protocol"
style="@style/TextStyle.Explain"
android:layout_width="wrap_content"
android:text="@string/title_user_protocol"
android:textColor="?attr/colorAccent" />
<TextView
style="@style/TextStyle.Explain"
android:layout_width="wrap_content"
android:text="@string/lab_and"
android:textColor="@color/xui_config_color_primary_text" />
<com.xuexiang.xui.widget.alpha.XUIAlphaTextView
android:id="@+id/tv_privacy_protocol"
style="@style/TextStyle.Explain"
android:layout_width="wrap_content"
android:text="@string/title_privacy_protocol"
android:textColor="?attr/colorAccent" />
</LinearLayout>
</FrameLayout>
</LinearLayout>

View File

@ -1141,6 +1141,40 @@
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pure_client_mode"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pure_client_mode_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_directly_to_client"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -852,4 +852,8 @@
<string name="service_address">Service Address</string>
<string name="service_address_hint">E.g: http://127.0.0.1:5000</string>
<string name="features_list">Features List</string>
<string name="pure_client_mode">Directly To Client</string>
<string name="pure_client_mode_tips">When starting the APP, it will directly enter the active control client</string>
<string name="exit_pure_client_mode">Exit pure client mode</string>
<string name="enabling_pure_client_mode">Do you want to quit the app immediately and start it manually to take effect in pure client mode?</string>
</resources>

View File

@ -853,4 +853,8 @@
<string name="service_address">服务地址</string>
<string name="service_address_hint">例如http://127.0.0.1:5000</string>
<string name="features_list">功能列表</string>
<string name="pure_client_mode">纯客户端模式</string>
<string name="pure_client_mode_tips">启动APP时直接进入主动控制·客户端</string>
<string name="exit_pure_client_mode">退出纯客户端模式</string>
<string name="enabling_pure_client_mode">是否立即退出App后手动启动以生效纯客户端模式</string>
</resources>