diff --git a/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt deleted file mode 100644 index e9283cf..0000000 --- a/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.myapplication - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.myapplication", appContext.packageName) - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 166f92e..a2f524c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,6 @@ android:name=".SplashActivity" android:exported="true" android:theme="@style/Theme.MyApp.Splash"> - @@ -33,7 +32,8 @@ + android:exported="true" + android:windowSoftInputMode="stateHidden|adjustNothing" /> (R.id.rootCoordinator) + // 动画 + itemAnim = AnimationUtils.loadAnimation(this, R.anim.item_slide_in_up) + //自动聚焦 + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + // 自动弹出键盘 + inputMessage.post { + inputMessage.isFocusable = true + inputMessage.isFocusableInTouchMode = true + inputMessage.requestFocus() + + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(inputMessage, InputMethodManager.SHOW_FORCED) + } + // 情话复制 + findViewById(R.id.love_words_1).setOnClickListener { + val text = it as TextView + val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("text", text.text)) + Toast.makeText(this, "Copy successfully", Toast.LENGTH_SHORT).show() + } + findViewById(R.id.love_words_2).setOnClickListener { + val text = it as TextView + val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("text", text.text)) + Toast.makeText(this, "Copy successfully", Toast.LENGTH_SHORT).show() + } + // 关闭按钮 findViewById(R.id.iv_close).setOnClickListener { finish() } + //输入框上移 + rootView.viewTreeObserver.addOnGlobalLayoutListener { + val r = Rect() + // 获取窗口可见区域 + rootView.getWindowVisibleDisplayFrame(r) + + val screenHeight = rootView.rootView.height + val visibleBottom = r.bottom + val keyboardHeight = screenHeight - visibleBottom + + // 这个阈值防止“状态栏/导航栏变化”被误认为键盘 + val isKeyboardVisible = keyboardHeight > screenHeight * 0.15 + + if (isKeyboardVisible) { + // 键盘高度为正,仅仅把 bottomPanel 抬上去 + bottomPanel.translationY = -keyboardHeight.toFloat() + + // 为了让最后一条消息不被挡住,可以给 scrollView 加个 paddingBottom + scrollView.setPadding( + scrollView.paddingLeft, + scrollView.paddingTop, + scrollView.paddingRight, + keyboardHeight + bottomPanel.height + ) + + // 再滚到底,保证能看到最新消息 + scrollToBottom() + } else { + // 键盘收起,复位 + bottomPanel.translationY = 0f + scrollView.setPadding( + scrollView.paddingLeft, + scrollView.paddingTop, + scrollView.paddingRight, + bottomPanel.height // 保持底部有一点空隙也可以按你需求调 + ) + } + } + + // 键盘发送 + inputMessage.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_SEND) { + // 走同一套发送逻辑 + sendMessage() + true + } else { + false + } + } + + + + // 2. 发送按钮点击事件 + btnSend.setOnClickListener { + sendMessage() + } } -} \ No newline at end of file + // 发送消息 + private fun sendMessage() { + val text = inputMessage.text.toString().trim() + + // 判空,没输入就不发 + if (text.isEmpty()) { + return + } + + // 2.1 把“对方”的消息(你刚输入的)显示出来 + addOtherPartyMessage(text) + + // 2.2 清空输入框 + inputMessage.setText("") + + val replyText = replyData.random() + + // 延迟执行我方回复 + scrollView.postDelayed({ + addOurMessage(replyText) + }, 500) + + inputMessage.isFocusable = true + inputMessage.isFocusableInTouchMode = true + + inputMessage.post { + // 重新获取焦点 + inputMessage.requestFocus() + // 光标移到文本末尾(虽然现在是空的,但习惯性加上) + inputMessage.setSelection(inputMessage.text.length) + + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(inputMessage, InputMethodManager.SHOW_IMPLICIT) + } + } + + // ======== 渲染“对方”消息:item_other_party_message ======== + private fun addOtherPartyMessage(text: String) { + // inflate 对方消息布局 + val itemView = layoutInflater.inflate( + R.layout.item_other_party_message, + listLayout, + false + ) + + val avatar = itemView.findViewById(R.id.avatar) + val container = itemView.findViewById(R.id.container) + + // 头像(你可以换成对方头像) + avatar.setImageResource(R.drawable.logo) + + // 显示内容 + container.text = text + + // 加到列表里 + listLayout.addView(itemView) + itemView.startAnimation(itemAnim) + + // 每加一条就滚动到底 + scrollToBottom() + } + + // ======== 渲染“我方”消息:item_our_news_message ======== + private fun addOurMessage(text: String) { + val itemView = layoutInflater.inflate( + R.layout.item_our_news_message, + listLayout, + false + ) + + val avatar = itemView.findViewById(R.id.avatar) + val container = itemView.findViewById(R.id.container) + + // 我方头像(随便放一个) + avatar.setImageResource(R.drawable.logo) + + container.text = text + + listLayout.addView(itemView) + itemView.startAnimation(itemAnim) + + scrollToBottom() + } + + // ======== 滚动到底部 ======== + private fun scrollToBottom() { + scrollView.post { + scrollView.fullScroll(View.FOCUS_DOWN) + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt b/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt index 891791a..c54095e 100644 --- a/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt +++ b/app/src/main/java/com/example/myapplication/ImeGuideActivity.kt @@ -5,28 +5,27 @@ import android.content.Intent import android.graphics.Color import android.os.Bundle import android.provider.Settings -import android.view.View +import android.util.Log import android.view.inputmethod.InputMethodManager -import android.widget.Button import android.widget.ImageView import android.widget.LinearLayout -import android.widget.FrameLayout import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import com.example.myapplication.MyInputMethodService - class ImeGuideActivity : AppCompatActivity() { - private lateinit var btnEnable: LinearLayout - private lateinit var btnSelect: LinearLayout - private lateinit var tvStep1Status: TextView// - private lateinit var tvStep2Status: TextView// - private lateinit var btnEnabledText: TextView// - private lateinit var btnSelectText: TextView// - private lateinit var btnEnabledimg: ImageView// - private lateinit var btnSelectimg: ImageView// + private val TAG = "ImeGuideActivity" + + // 改成可空,避免 findViewById 返回 null 时直接 NPE + private var btnEnable: LinearLayout? = null + private var btnSelect: LinearLayout? = null + private var tvStep1Status: TextView? = null + private var tvStep2Status: TextView? = null + private var btnEnabledText: TextView? = null + private var btnSelectText: TextView? = null + private var btnEnabledimg: ImageView? = null + private var btnSelectimg: ImageView? = null private var imeObserver: android.database.ContentObserver? = null @@ -34,68 +33,79 @@ class ImeGuideActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_ime_guide) - // // 设置关闭按钮点击事件 - // findViewById(R.id.iv_close).setOnClickListener { - // finish() - // } + Log.d(TAG, "onCreate") - btnEnable = findViewById(R.id.enabled)//btn启用输入法 - btnSelect = findViewById(R.id.select)//btn选择输入法 - tvStep1Status = findViewById(R.id.Steps)//第一步的提示 - tvStep2Status = findViewById(R.id.stepTips)//第二步的提示 - btnEnabledText = findViewById(R.id.btnEnabledText)//启用输入法按钮文字 - btnSelectText = findViewById(R.id.btnSelectText)//选择输入法按钮文字 - btnEnabledimg = findViewById(R.id.btnEnabledimg)//启用输入法按钮图片 - btnSelectimg = findViewById(R.id.btnSelectimg)//选择输入法按钮图片 - - // // 第一步:启用输入法 - // btnEnable.setOnClickListener { - // startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)) - // } - - - // // 第二步:切换输入法 - // btnSelect.setOnClickListener { - // val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - // imm.showInputMethodPicker() - // } + btnEnable = findViewById(R.id.enabled) // btn启用输入法 + btnSelect = findViewById(R.id.select) // btn选择输入法 + tvStep1Status = findViewById(R.id.Steps) // 第一步的提示 + tvStep2Status = findViewById(R.id.stepTips) // 第二步的提示 + btnEnabledText = findViewById(R.id.btnEnabledText) // 启用输入法按钮文字 + btnSelectText = findViewById(R.id.btnSelectText) // 选择输入法按钮文字 + btnEnabledimg = findViewById(R.id.btnEnabledimg) // 启用输入法按钮图片 + btnSelectimg = findViewById(R.id.btnSelectimg) // 选择输入法按钮图片 } override fun onResume() { super.onResume() - refreshStatus() - registerImeObserver() + try { + refreshStatus() + } catch (e: Exception) { + Log.e(TAG, "refreshStatus 崩了", e) + } + + try { + registerImeObserver() + } catch (e: Exception) { + Log.e(TAG, "registerImeObserver 崩了", e) + } } override fun onPause() { super.onPause() - unregisterImeObserver() + try { + unregisterImeObserver() + } catch (e: Exception) { + Log.e(TAG, "unregisterImeObserver 崩了", e) + } } private fun registerImeObserver() { if (imeObserver != null) return - + imeObserver = object : android.database.ContentObserver( android.os.Handler(android.os.Looper.getMainLooper()) ) { override fun onChange(selfChange: Boolean) { super.onChange(selfChange) - refreshStatus() + try { + refreshStatus() + } catch (e: Exception) { + Log.e(TAG, "onChange -> refreshStatus 崩了", e) + } } } - - contentResolver.registerContentObserver( - android.provider.Settings.Secure.getUriFor( - android.provider.Settings.Secure.DEFAULT_INPUT_METHOD - ), - false, - imeObserver!! - ) + + try { + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), + false, + imeObserver!! + ) + } catch (e: SecurityException) { + // 部分 ROM/系统可能在这里抛异常 + Log.e(TAG, "registerContentObserver SecurityException", e) + } catch (e: Exception) { + Log.e(TAG, "registerContentObserver 其他异常", e) + } } - + private fun unregisterImeObserver() { imeObserver?.let { - contentResolver.unregisterContentObserver(it) + try { + contentResolver.unregisterContentObserver(it) + } catch (e: Exception) { + Log.e(TAG, "unregisterContentObserver 异常", e) + } } imeObserver = null } @@ -104,100 +114,144 @@ class ImeGuideActivity : AppCompatActivity() { private fun refreshStatus() { val enabled = isImeEnabled() val selected = isImeSelected() - + + // 把所有 view 拿成局部变量并判空,避免 NPE + val enableLayout = btnEnable + val selectLayout = btnSelect + val step1 = tvStep1Status + val step2 = tvStep2Status + val enableText = btnEnabledText + val selectText = btnSelectText + val enableImg = btnEnabledimg + val selectImg = btnSelectimg + + if (enableLayout == null || selectLayout == null || + step1 == null || step2 == null || + enableText == null || selectText == null || + enableImg == null || selectImg == null + ) { + Log.e(TAG, "有 View 为 null,检查 activity_ime_guide.xml 的 id 是否匹配") + return + } + // 根据状态设置按钮的点击行为 if (enabled) { // 输入法已启用时,禁用启用按钮的点击事件 - btnEnable.setOnClickListener(null) + enableLayout.setOnClickListener(null) } else { // 输入法未启用时,设置启用按钮的点击事件 - btnEnable.setOnClickListener { + enableLayout.setOnClickListener { startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)) } } if (selected) { // 输入法已切换时,禁用选择按钮的点击事件 - btnSelect.setOnClickListener(null) + selectLayout.setOnClickListener(null) } else { // 输入法未切换时,设置选择按钮的点击事件 - btnSelect.setOnClickListener { + selectLayout.setOnClickListener { val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.showInputMethodPicker() } } - if(!enabled &&!selected) { - btnEnable.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) - btnSelect.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) - btnEnabledText.setTextColor(Color.parseColor("#FFFFFF")) - btnSelectText.setTextColor(Color.parseColor("#FFFFFF")) - btnEnabledimg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) - btnSelectimg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) - tvStep1Status.text = "Step one" - tvStep1Status.text = "Step one" - tvStep2Status.text = "Check to enable key of love" - }else if(!enabled && selected){ - btnEnable.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) - btnSelect.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) - btnEnabledimg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) - btnSelectimg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) - btnEnabledText.setTextColor(Color.parseColor("#FFFFFF")) - btnSelectText.setTextColor(Color.parseColor("#FFFFFF")) - tvStep1Status.text = "Step one" - tvStep2Status.text = "Check to enable key of love" - }else if(enabled && !selected){ - btnEnable.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) - btnSelect.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) - btnEnabledimg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) - btnSelectimg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) - btnEnabledText.setTextColor(Color.parseColor("#A1A1A1")) - btnSelectText.setTextColor(Color.parseColor("#FFFFFF")) - tvStep1Status.text = "Step two" - tvStep2Status.text = "Select key of love as your default input method" - }else if(enabled && selected){ - btnEnable.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) - btnSelect.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) - btnEnabledimg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) - btnSelectimg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) - btnEnabledText.setTextColor(Color.parseColor("#A1A1A1")) - btnSelectText.setTextColor(Color.parseColor("#A1A1A1")) - tvStep1Status.text = "Completed" - tvStep2Status.text = "You have completed the relevant Settings" + if (!enabled && !selected) { + enableLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) + selectLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) + enableText.setTextColor(Color.parseColor("#FFFFFF")) + selectText.setTextColor(Color.parseColor("#FFFFFF")) + enableImg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) + selectImg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) + step1.text = "Step one" + step2.text = "Check to enable key of love" + } else if (!enabled && selected) { + enableLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) + selectLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) + enableImg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) + selectImg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) + enableText.setTextColor(Color.parseColor("#FFFFFF")) + selectText.setTextColor(Color.parseColor("#FFFFFF")) + step1.text = "Step one" + step2.text = "Check to enable key of love" + } else if (enabled && !selected) { + enableLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) + selectLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_unfinished) + enableImg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) + selectImg.setImageResource(R.drawable.ime_guide_activity_btn_unfinished_img) + enableText.setTextColor(Color.parseColor("#A1A1A1")) + selectText.setTextColor(Color.parseColor("#FFFFFF")) + step1.text = "Step two" + step2.text = "Select key of love as your default input method" + } else if (enabled && selected) { + enableLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) + selectLayout.background = getDrawable(R.drawable.ime_guide_activity_btn_completed) + enableImg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) + selectImg.setImageResource(R.drawable.ime_guide_activity_btn_completed_img) + enableText.setTextColor(Color.parseColor("#A1A1A1")) + selectText.setTextColor(Color.parseColor("#A1A1A1")) + step1.text = "Completed" + step2.text = "You have completed the relevant Settings" Toast.makeText(this, "The input method is all set!", Toast.LENGTH_SHORT).show() - startActivity(Intent(this, GuideActivity::class.java)) + try { + startActivity(Intent(this, GuideActivity::class.java)) + } catch (e: Exception) { + Log.e(TAG, "启动 GuideActivity 失败,检查是否在 Manifest 中声明", e) + } finish() } } - /** 是否启用了本输入法 */ - private fun isImeEnabled(): Boolean { - val enabledImes = Settings.Secure.getString( - contentResolver, - Settings.Secure.ENABLED_INPUT_METHODS - ) ?: return false - - // 用真正的类,而不是手写字符串 - val myImeComponent = ComponentName(this, MyInputMethodService::class.java) - // 系统存的是 flattenToString() 的格式 - val myImeId = myImeComponent.flattenToString() - - // enabledImes 是一个用 ":" 分隔的列表 - return enabledImes.split(':').contains(myImeId) + /** 是否启用了本输入法 */ + private fun isImeEnabled(): Boolean { + return try { + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + val myComponent = ComponentName(this, MyInputMethodService::class.java) + + val result = imm.enabledInputMethodList.any { imeInfo -> + imeInfo.packageName == myComponent.packageName && + imeInfo.serviceName == myComponent.className + } + + Log.d(TAG, "isImeEnabled = $result") + result + } catch (e: Exception) { + Log.e(TAG, "isImeEnabled 出错", e) + false } + } - /** 是否已切换为当前输入法 */ - private fun isImeSelected(): Boolean { - val currentIme = Settings.Secure.getString( + /** 是否已切换为当前输入法 */ + private fun isImeSelected(): Boolean { + return try { + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + val myComponent = ComponentName(this, MyInputMethodService::class.java) + + val currentImeId = Settings.Secure.getString( contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD ) ?: return false - - val myImeComponent = ComponentName(this, MyInputMethodService::class.java) - val myImeId = myImeComponent.flattenToString() - - // 直接用同一种格式比对 - return currentIme == myImeId + + Log.d(TAG, "DEFAULT_INPUT_METHOD = $currentImeId") + + // 找到“当前默认 IME”对应的 InputMethodInfo + val currentImeInfo = imm.enabledInputMethodList.firstOrNull { imeInfo -> + imeInfo.id == currentImeId + } + + if (currentImeInfo == null) { + Log.d(TAG, "currentImeInfo == null") + return false + } + + val isMine = currentImeInfo.packageName == myComponent.packageName && + currentImeInfo.serviceName == myComponent.className + + Log.d(TAG, "isImeSelected = $isMine") + isMine + } catch (e: Exception) { + Log.e(TAG, "isImeSelected 出错", e) + false } - + } } diff --git a/app/src/main/java/com/example/myapplication/MainActivity.kt b/app/src/main/java/com/example/myapplication/MainActivity.kt index 670d273..a19e4dc 100644 --- a/app/src/main/java/com/example/myapplication/MainActivity.kt +++ b/app/src/main/java/com/example/myapplication/MainActivity.kt @@ -34,15 +34,21 @@ class MainActivity : AppCompatActivity() { // 5. 添加导航监听(用于某些 Fragment 隐藏底部导航栏) navController.addOnDestinationChangedListener { _, destination, _ -> - when (destination.id) { - R.id.rechargeFragment, R.id.goldCoinRechargeFragment,R.id.PersonalSettings,R.id.MySkin,R.id.searchFragment,R.id.MyKeyboard,R.id.searchResultFragment,R.id.keyboardDetailFragment,R.id.feedbackFragment,R.id.notificationFragment-> { - bottomNav.visibility = View.GONE - } - else -> { - bottomNav.visibility = View.VISIBLE - } + + // 只有这些页面显示 BottomNav + val pagesWithBottomNav = setOf( + R.id.mineFragment, + R.id.homeFragment, + R.id.shopFragment + ) + + if (destination.id in pagesWithBottomNav) { + bottomNav.visibility = View.VISIBLE + } else { + bottomNav.visibility = View.GONE } } + // 6. 检查是否有导航参数,处理从键盘跳转过来的请求 handleNavigationFromIntent() diff --git a/app/src/main/java/com/example/myapplication/MyInputMethodService.kt b/app/src/main/java/com/example/myapplication/MyInputMethodService.kt index ade91fc..56f9786 100644 --- a/app/src/main/java/com/example/myapplication/MyInputMethodService.kt +++ b/app/src/main/java/com/example/myapplication/MyInputMethodService.kt @@ -31,6 +31,11 @@ import com.example.myapplication.keyboard.MainKeyboard import com.example.myapplication.keyboard.NumberKeyboard import com.example.myapplication.keyboard.SymbolKeyboard import com.example.myapplication.keyboard.AiKeyboard +import android.text.InputType +import android.view.KeyEvent +import android.os.SystemClock + + class MyInputMethodService : InputMethodService(), KeyboardEnvironment { @@ -119,6 +124,16 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { ColorStateList.valueOf(Color.TRANSPARENT) private set + // 键盘关闭 + override fun getInputConnection(): InputConnection? { + return currentInputConnection + } + + override fun hideKeyboard() { + currentInputConnection?.finishComposingText() + requestHideSelf(0) + } + // 副字符映射表(主键盘上滑) private val swipeAltMap: Map = mapOf( 'q' to '1', @@ -204,6 +219,8 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { tryStartForegroundSafe() } + + private fun createNotificationChannelIfNeeded() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( @@ -245,7 +262,43 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { override fun onStartInputView(info: EditorInfo?, restarting: Boolean) { super.onStartInputView(info, restarting) isInputViewShownFlag = true - showMainKeyboard() + + // 根据当前输入框类型自动切换键盘 + if (isNumberEditor(info)) { + showNumberKeyboard() + } else { + showMainKeyboard() + } + } + + //判断当前输入框是否是数字相关输入框: + private fun isNumberEditor(info: EditorInfo?): Boolean { + if (info == null) return false + + val inputType = info.inputType + val clazz = inputType and InputType.TYPE_MASK_CLASS + val variation = inputType and InputType.TYPE_MASK_VARIATION + + // 纯数字、电话输入 + if (clazz == InputType.TYPE_CLASS_NUMBER || clazz == InputType.TYPE_CLASS_PHONE) { + return true + } + + // 数字密码:numberPassword + if (clazz == InputType.TYPE_CLASS_TEXT && + variation == InputType.TYPE_TEXT_VARIATION_PASSWORD && + (inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL != 0 || + inputType and InputType.TYPE_NUMBER_FLAG_SIGNED != 0) + ) { + return true + } + + return false + } + + override fun onFinishInput() { + super.onFinishInput() + clearEditorState() } override fun onFinishInputView(finishingInput: Boolean) { @@ -253,6 +306,9 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { isInputViewShownFlag = false stopRepeatDelete() + // 清理本次输入状态 + clearEditorState() + mainHandler.postDelayed({ if (!isInputViewShownFlag) { try { @@ -283,12 +339,22 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { ) mainKeyboardView = mainKeyboard!!.rootView - // 这里再给删除键挂长按连删(用你自己的 attachRepeatDeleteInternal) + // 初始状态:隐藏联想条,显示控制面板 + mainKeyboardView + ?.findViewById(R.id.completion_scroll) + ?.visibility = View.GONE + + mainKeyboardView + ?.findViewById(R.id.control_layout) + ?.visibility = View.VISIBLE + + // 删除键长按连删 val delId = resources.getIdentifier("key_del", "id", packageName) mainKeyboardView?.findViewById(delId)?.let { attachRepeatDeleteInternal(it) } } return mainKeyboard!! } + private fun ensureNumberKeyboard(): NumberKeyboard { if (numberKeyboard == null) { @@ -398,26 +464,39 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 删除一个字符(原 handleBackspace) override fun deleteOne() { val ic = currentInputConnection ?: return - - ic.deleteSurroundingText(1, 0) - + + // 1️⃣ 发送一个 DEL 按键(DOWN + UP),让客户端有机会拦截 + ic.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + ic.sendKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)) + + // 如果你担心有些 EditText 不处理 DEL,可以加一个兜底: + // ic.deleteSurroundingText(1, 0) + + // 2️⃣ 你原来的逻辑可以继续保留 val prefix = getCurrentWordPrefix() updateCompletionsAndRender(prefix) - + playKeyClick() } + - // 发送(原 performSendAction) + // 发送(标准 SEND + 回车 fallback) override fun performSendAction() { val ic = currentInputConnection ?: return + // 1. 尝试执行标准发送动作(IME_ACTION_SEND) val handled = ic.performEditorAction(EditorInfo.IME_ACTION_SEND) + if (!handled) { + // 2. 如果输入框不支持 SEND,则退回到插入换行 ic.commitText("\n", 1) } + playKeyClick() + clearEditorState() } + // 按键音效 override fun playKeyClick() { try { @@ -436,15 +515,31 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { // 统一处理补全/联想 override fun updateCompletionsAndRender(prefix: String) { + val ic = currentInputConnection + + // 先判断整个编辑框是不是“真的空” + val beforeAll = ic?.getTextBeforeCursor(256, 0)?.toString().orEmpty() + val afterAll = ic?.getTextAfterCursor(256, 0)?.toString().orEmpty() + val editorReallyEmpty = beforeAll.isEmpty() && afterAll.isEmpty() + + // 当前输入前缀 currentInput.clear() currentInput.append(prefix) - + + // 如果整个编辑框都是空的:直接清空联想 & 刷新 UI,什么都不算 + if (editorReallyEmpty) { + clearEditorState() + return + } + + // 否则再去算 lastWord val lastWord = getPrevWordBeforeCursor() - + Thread { val list = try { if (prefix.isEmpty()) { if (lastWord == null) { + // 这里也保持 emptyList,防止空前缀 + 无上文时走全局高频随机词 emptyList() } else { suggestWithBigram("", lastWord, topK = 20) @@ -468,13 +563,14 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { emptyList() } } - + mainHandler.post { completionSuggestions = suggestionStats.sortByCount(list.distinct().take(20)) showCompletionSuggestions() } }.start() } + // 显示自动完成建议(布局不变) private fun showCompletionSuggestions() { @@ -482,14 +578,43 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { val suggestionsView = mainKeyboardView?.findViewById(R.id.completion_suggestions) + // 新增:联想滚动条 & 控制面板 + val completionScroll = + mainKeyboardView?.findViewById(R.id.completion_scroll) + val controlLayout = + mainKeyboardView?.findViewById(R.id.control_layout) + val suggestions = (0..20).map { i -> mainKeyboardView?.findViewById( resources.getIdentifier("suggestion_$i", "id", packageName) ) } - // suggestion_0 显示 prefix + // 当前前缀 val prefix = getCurrentWordPrefix() + val hasPrefix = prefix.isNotEmpty() + val hasSuggestions = completionSuggestions.isNotEmpty() + + // 判断光标前是否有任何输入(“没有输入”的判断) + val hasAnyInput = try { + val ic = currentInputConnection + val before = ic?.getTextBeforeCursor(1, 0) + !before.isNullOrEmpty() + } catch (_: Throwable) { + false + } + + // 当:没有前缀 && 没有联想/补全词 && 光标前也没有任何输入 + // -> 隐藏联想条,显示控制面板 + if (!hasPrefix && !hasSuggestions && !hasAnyInput) { + completionScroll?.visibility = View.GONE + controlLayout?.visibility = View.VISIBLE + } else { + completionScroll?.visibility = View.VISIBLE + controlLayout?.visibility = View.GONE + } + + // suggestion_0 显示 prefix suggestions[0]?.text = prefix suggestions[0]?.visibility = if (prefix.isEmpty()) View.GONE else View.VISIBLE suggestions[0]?.setOnClickListener { @@ -500,11 +625,13 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { suggestions.drop(1).forEachIndexed { index, textView -> textView?.text = completionSuggestions.getOrNull(index) ?: "" if (index < completionSuggestions.size) { + textView?.visibility = View.VISIBLE textView?.setOnClickListener { suggestionStats.incClick(completionSuggestions[index]) insertCompletion(completionSuggestions[index]) } } else { + textView?.visibility = View.GONE textView?.setOnClickListener(null) } } @@ -513,6 +640,7 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { } } + // 自动滚回到最左边 private fun scrollSuggestionsToStart() { val sv = mainKeyboardView?.findViewById(R.id.completion_scroll) @@ -741,4 +869,71 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment { out.reverse() return out } + + override fun onUpdateSelection( + oldSelStart: Int, + oldSelEnd: Int, + newSelStart: Int, + newSelEnd: Int, + candidatesStart: Int, + candidatesEnd: Int + ) { + super.onUpdateSelection( + oldSelStart, + oldSelEnd, + newSelStart, + newSelEnd, + candidatesStart, + candidatesEnd + ) + + val ic = currentInputConnection ?: return + + // 这里不用取太多,1 个字符就够判断是否为空了 + val before = ic.getTextBeforeCursor(1, 0)?.toString().orEmpty() + val after = ic.getTextAfterCursor(1, 0)?.toString().orEmpty() + + // 当编辑框光标前后都没有任何字符,说明真的完全空了 + if (before.isEmpty() && after.isEmpty()) { + clearEditorState() + } + } + + + // 清理本次编辑框相关的状态(光标、联想、长按等) + private fun clearEditorState() { + // 1. 文本联想/补全相关 + currentInput.clear() + completionSuggestions = emptyList() + lastWordForLM = null + + // 2. Shift 状态 + isShiftOn = false + + // 3. 停止长按删除 + stopRepeatDelete() + + // 4. UI:联想条隐藏 & 控制面板显示 + mainHandler.post { + val completionScroll = + mainKeyboardView?.findViewById(R.id.completion_scroll) + val controlLayout = + mainKeyboardView?.findViewById(R.id.control_layout) + + completionScroll?.visibility = View.GONE + controlLayout?.visibility = View.VISIBLE + + // 再让联想区域里的文本都清空一下 + val suggestions = (0..20).map { i -> + mainKeyboardView?.findViewById( + resources.getIdentifier("suggestion_$i", "id", packageName) + ) + } + suggestions.forEach { tv -> + tv?.text = "" + tv?.visibility = View.GONE + tv?.setOnClickListener(null) + } + } + } } diff --git a/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt b/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt index 0227ee5..2ce828b 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/KeyboardEnvironment.kt @@ -5,6 +5,7 @@ import android.content.res.ColorStateList import android.media.AudioManager import android.os.Handler import android.view.LayoutInflater +import android.view.inputmethod.InputConnection interface KeyboardEnvironment { val ctx: Context @@ -27,6 +28,10 @@ interface KeyboardEnvironment { fun deleteOne() fun performSendAction() + //键盘关闭 + fun getInputConnection(): InputConnection? + fun hideKeyboard() + // 键盘切换 fun showMainKeyboard() fun showNumberKeyboard() diff --git a/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt index 192990b..41f9cc0 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/MainKeyboard.kt @@ -40,8 +40,8 @@ class MainKeyboard( env.ctx.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator private fun vibrateKey( - duration: Long = 20L, // 时间:10~40 推荐 - amplitude: Int = 150 // 1~255,100~150 最舒服 + duration: Long = 30L, // 时间:10~40 推荐 + amplitude: Int = 255 // 1~255,100~150 最舒服 ) { val v = vibrator ?: return if (!v.hasVibrator()) return @@ -93,7 +93,8 @@ class MainKeyboard( "key_del", "key_up", "key_123", - "key_ai" + "key_ai", + "Key_collapse" ) for (idName in others) { applyKeyBackground(root, idName) @@ -192,6 +193,13 @@ class MainKeyboard( env.deleteOne() } + //关闭键盘 + rootView.findViewById(res.getIdentifier("collapse_button", "id", pkg)) + ?.setOnClickListener { + vibrateKey() // 如果这个方法在当前类里有 + env.hideKeyboard() + } + // 切换数字键盘 view.findViewById(res.getIdentifier("key_123", "id", pkg)) ?.setOnClickListener { diff --git a/app/src/main/java/com/example/myapplication/keyboard/NumberKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/NumberKeyboard.kt index dd84b52..f5f3a35 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/NumberKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/NumberKeyboard.kt @@ -35,8 +35,8 @@ class NumberKeyboard( env.ctx.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator private fun vibrateKey( - duration: Long = 20L, // 时间:10~40 推荐 - amplitude: Int = 150 // 1~255,100~150 最舒服 + duration: Long = 30L, // 时间:10~40 推荐 + amplitude: Int = 255 // 1~255,100~150 最舒服 ) { val v = vibrator ?: return if (!v.hasVibrator()) return @@ -111,7 +111,8 @@ class NumberKeyboard( "key_ai", "key_space", "key_send", - "key_del" + "key_del", + "Key_collapse" ) others.forEach { idName -> applyKeyBackground(root, idName) @@ -246,6 +247,13 @@ class NumberKeyboard( vibrateKey() env.deleteOne() } + + //关闭键盘 + rootView.findViewById(res.getIdentifier("collapse_button", "id", pkg)) + ?.setOnClickListener { + vibrateKey() // 如果这个方法在当前类里有 + env.hideKeyboard() + } } // ================= 按键触摸 & 预览 ================= diff --git a/app/src/main/java/com/example/myapplication/keyboard/SymbolKeyboard.kt b/app/src/main/java/com/example/myapplication/keyboard/SymbolKeyboard.kt index 65d2cba..8e26cac 100644 --- a/app/src/main/java/com/example/myapplication/keyboard/SymbolKeyboard.kt +++ b/app/src/main/java/com/example/myapplication/keyboard/SymbolKeyboard.kt @@ -35,8 +35,8 @@ class SymbolKeyboard( env.ctx.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator private fun vibrateKey( - duration: Long = 20L, // 时间:10~40 推荐 - amplitude: Int = 150 // 1~255,100~150 最舒服 + duration: Long = 30L, // 时间:10~40 推荐 + amplitude: Int = 255 // 1~255,100~150 最舒服 ) { val v = vibrator ?: return if (!v.hasVibrator()) return @@ -121,7 +121,8 @@ class SymbolKeyboard( "key_abc", "key_ai", "key_space", - "key_send" + "key_send", + "Key_collapse" ) others.forEach { idName -> @@ -264,6 +265,13 @@ class SymbolKeyboard( vibrateKey() env.showAiKeyboard() } + + //关闭键盘 + rootView.findViewById(res.getIdentifier("collapse_button", "id", pkg)) + ?.setOnClickListener { + vibrateKey() // 如果这个方法在当前类里有 + env.hideKeyboard() + } } // ================== 触摸 + 预览 ================== diff --git a/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt b/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt index f83f237..6cb72e5 100644 --- a/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/home/HomeFragment.kt @@ -66,7 +66,9 @@ class HomeFragment : Fragment() { } //输入法激活跳转 view.findViewById(R.id.floatingImage).setOnClickListener { - startActivity(Intent(requireContext(), ImeGuideActivity::class.java)) + if (isAdded) { + startActivity(Intent(requireActivity(), ImeGuideActivity::class.java)) + } } scrim = view.findViewById(R.id.view_scrim) diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt new file mode 100644 index 0000000..fb3c8b8 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordEmailFragment.kt @@ -0,0 +1,31 @@ +// 忘记密码邮箱输入页面 +package com.example.myapplication.ui.login + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.myapplication.R +import com.google.android.material.textfield.TextInputLayout +import android.widget.TextView +import androidx.navigation.fragment.findNavController + +class ForgetPasswordEmailFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_forget_password_email, container, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + //验证码页面 + view.findViewById(R.id.nextstep).setOnClickListener { + findNavController().navigate(R.id.action_forgetPasswordEmailFragment_to_forgetPasswordVerifyFragment) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt new file mode 100644 index 0000000..04d0a96 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordResetFragment.kt @@ -0,0 +1,21 @@ +// 忘记密码重置密码页面 +package com.example.myapplication.ui.login + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.myapplication.R + +class ForgetPasswordResetFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_forget_password_reset, container, false) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt new file mode 100644 index 0000000..15d3047 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/ForgetPasswordVerifyFragment.kt @@ -0,0 +1,116 @@ +// 忘记密码验证码输入页面 +package com.example.myapplication.ui.login + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.Fragment +import com.example.myapplication.R +import android.widget.TextView +import androidx.navigation.fragment.findNavController + + +class ForgetPasswordVerifyFragment : Fragment() { + + private lateinit var codeInputs: List + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_forget_password_verify, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + //验证码页面 + view.findViewById< + TextView>(R.id.nextstep).setOnClickListener { + findNavController().navigate(R.id.action_forgetPasswordVerifyFragment_to_forgetPasswordResetFragment) + } + + codeInputs = listOf( + view.findViewById(R.id.et_code_1), + view.findViewById(R.id.et_code_2), + view.findViewById(R.id.et_code_3), + view.findViewById(R.id.et_code_4), + view.findViewById(R.id.et_code_5), + view.findViewById(R.id.et_code_6) + ) + + setupVerifyCodeInputs() + } + + private fun setupVerifyCodeInputs() { + + codeInputs.forEachIndexed { index, editText -> + + // 输入监听:自动跳到下一格 + editText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable?) { + val text = s?.toString() ?: "" + + // 保证只有1位 + if (text.length > 1) { + editText.setText(text.last().toString()) + editText.setSelection(1) + } + + if (text.isNotEmpty() && index < codeInputs.size - 1) { + codeInputs[index + 1].requestFocus() + } + } + }) + + // 关键:监听删除键,通过 EditorAction 捕捉硬件删除 + editText.setOnKeyListener { _, keyCode, event -> + if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DEL) { + + // 当前为空 -> 回到上一格 + if (editText.text.isEmpty() && index > 0) { + val prev = codeInputs[index - 1] + prev.requestFocus() + prev.text.clear() + return@setOnKeyListener true + } + } + false + } + + // 特别关键:有些系统不会触发 KEYCODE_DEL,这个可以兜底捕捉删除动作 + editText.setOnEditorActionListener { _, _, event -> + if (event != null && + event.action == KeyEvent.ACTION_DOWN && + event.keyCode == KeyEvent.KEYCODE_DEL + ) { + if (editText.text.isEmpty() && index > 0) { + val prev = codeInputs[index - 1] + prev.requestFocus() + prev.text.clear() + true + } else false + } else false + } + } + } + + + + + + // 获取完整验证码 + private fun getVerifyCode(): String { + return codeInputs.joinToString("") { it.text.toString() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt new file mode 100644 index 0000000..a325829 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/LoginFragment.kt @@ -0,0 +1,82 @@ +package com.example.myapplication.ui.login + +import android.os.Bundle +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageView +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import android.widget.FrameLayout +import android.widget.TextView +import com.example.myapplication.R +import com.google.android.material.button.MaterialButton + +class LoginFragment : Fragment() { + + private lateinit var passwordEditText: EditText + private lateinit var toggleImageView: ImageView + private lateinit var loginButton: MaterialButton // 如果你 XML 里有这个按钮 id: btn_login + + private var isPasswordVisible = false + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_login, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // 注册 + view.findViewById(R.id.tv_signup).setOnClickListener { + findNavController().navigate(R.id.action_mineFragment_to_registerFragment) + } + // 忘记密码 + view.findViewById(R.id.tv_forgot_password).setOnClickListener { + findNavController().navigate(R.id.action_loginFragment_to_forgetPasswordEmailFragment) + } + // 返回按钮 + view.findViewById(R.id.iv_close).setOnClickListener { + parentFragmentManager.popBackStack() + } + // 绑定控件(id 必须和 xml 里的一样) + passwordEditText = view.findViewById(R.id.et_password) + toggleImageView = view.findViewById(R.id.iv_toggle) + // loginButton = view.findViewById(R.id.btn_login) // 如果没有这个按钮就把这一行和变量删了 + + // 初始是隐藏密码状态 + passwordEditText.inputType = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + + toggleImageView.setOnClickListener { + isPasswordVisible = !isPasswordVisible + + if (isPasswordVisible) { + // 显示密码 + passwordEditText.inputType = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + toggleImageView.setImageResource(R.drawable.display) + } else { + // 隐藏密码 + passwordEditText.inputType = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + toggleImageView.setImageResource(R.drawable.hide) + } + + // 保持光标在末尾 + passwordEditText.setSelection(passwordEditText.text?.length ?: 0) + } + + // // 登录按钮逻辑你自己填 + // loginButton.setOnClickListener { + // val pwd = passwordEditText.text?.toString().orEmpty() + // // TODO: 登录处理 + // } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt b/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt new file mode 100644 index 0000000..54aa6c1 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/RegisterFragment.kt @@ -0,0 +1,31 @@ +package com.example.myapplication.ui.login + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import android.widget.FrameLayout +import android.widget.TextView +import com.example.myapplication.R + +class RegisterFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_register, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // 返回按钮 + view.findViewById(R.id.iv_close).setOnClickListener { + parentFragmentManager.popBackStack() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt b/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt index f4d635c..9214004 100644 --- a/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/mine/MineFragment.kt @@ -55,5 +55,10 @@ class MineFragment : Fragment() { findNavController().navigate(R.id.action_mineFragment_to_notificationFragment) } + //隐私政策 + view.findViewById(R.id.click_Privacy).setOnClickListener { + findNavController().navigate(R.id.action_mineFragment_to_loginFragment) + } + } } diff --git a/app/src/main/res/anim/item_slide_in_up.xml b/app/src/main/res/anim/item_slide_in_up.xml new file mode 100644 index 0000000..a11766b --- /dev/null +++ b/app/src/main/res/anim/item_slide_in_up.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/acttivity_guide_btn_bg.xml b/app/src/main/res/drawable/acttivity_guide_btn_bg.xml new file mode 100644 index 0000000..9b823f7 --- /dev/null +++ b/app/src/main/res/drawable/acttivity_guide_btn_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/code_box_bg.xml b/app/src/main/res/drawable/code_box_bg.xml new file mode 100644 index 0000000..d3dde44 --- /dev/null +++ b/app/src/main/res/drawable/code_box_bg.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/display.png b/app/src/main/res/drawable/display.png new file mode 100644 index 0000000..67fafe5 Binary files /dev/null and b/app/src/main/res/drawable/display.png differ diff --git a/app/src/main/res/drawable/hide.png b/app/src/main/res/drawable/hide.png new file mode 100644 index 0000000..8f2efd3 Binary files /dev/null and b/app/src/main/res/drawable/hide.png differ diff --git a/app/src/main/res/drawable/input_box_bg.xml b/app/src/main/res/drawable/input_box_bg.xml new file mode 100644 index 0000000..9d43401 --- /dev/null +++ b/app/src/main/res/drawable/input_box_bg.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/login_bg.png b/app/src/main/res/drawable/login_bg.png new file mode 100644 index 0000000..c23610e Binary files /dev/null and b/app/src/main/res/drawable/login_bg.png differ diff --git a/app/src/main/res/drawable/login_btn_bg.xml b/app/src/main/res/drawable/login_btn_bg.xml new file mode 100644 index 0000000..b21c780 --- /dev/null +++ b/app/src/main/res/drawable/login_btn_bg.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/login_content_bg.xml b/app/src/main/res/drawable/login_content_bg.xml new file mode 100644 index 0000000..d9fcb65 --- /dev/null +++ b/app/src/main/res/drawable/login_content_bg.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/login_icon.png b/app/src/main/res/drawable/login_icon.png new file mode 100644 index 0000000..5bbdce9 Binary files /dev/null and b/app/src/main/res/drawable/login_icon.png differ diff --git a/app/src/main/res/drawable/other_party_message.xml b/app/src/main/res/drawable/other_party_message.xml new file mode 100644 index 0000000..3b0ceb2 --- /dev/null +++ b/app/src/main/res/drawable/other_party_message.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/our_news.xml b/app/src/main/res/drawable/our_news.xml new file mode 100644 index 0000000..768015e --- /dev/null +++ b/app/src/main/res/drawable/our_news.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_guide.xml b/app/src/main/res/layout/activity_guide.xml index 2034a6b..d2488af 100644 --- a/app/src/main/res/layout/activity_guide.xml +++ b/app/src/main/res/layout/activity_guide.xml @@ -1,39 +1,159 @@ - - - - - - + - - - + android:src="@drawable/login_bg" + android:scaleType="fitXY" + android:adjustViewBounds="true" /> + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + +