← 返回文章列表

SMSSlide

build.gradle.kts

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.example.smsslide"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.smsslide"
        minSdk = 34
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    buildFeatures {
        compose = true
        viewBinding = true
    }
}

dependencies {

    implementation("androidx.cardview:cardview:1.0.0")
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    implementation(libs.androidx.recyclerview)
    implementation(libs.androidx.appcompat)
    implementation(libs.firebase.firestore.ktx)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
}

入口文件

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- Request permission to read SMS messages -->
    <uses-permission android:name="android.permission.READ_SMS"
        tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" />

    <!-- Request permission to send SMS messages -->
    <uses-permission android:name="android.permission.RECEIVE_SMS"
        tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" />
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SMSSlide"
        >
        <activity android:name=".DeletedSmsActivity"
            android:theme="@style/AppTheme">

        </activity>
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@style/Theme.SMSSlide">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Java 文件夹

MainActivity.kt

package com.example.smsslide

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import android.Manifest
import android.util.Log
import android.content.pm.PackageManager
import android.database.Cursor
import android.provider.Telephony
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.recyclerview.widget.PagerSnapHelper
import java.text.SimpleDateFormat
import java.util.*


class MainActivity : ComponentActivity() {

    private lateinit var smsList: MutableList<SmsItem>
    private var deletedSmsList = mutableListOf<SmsItem>() // List to hold recently deleted SMS
    private lateinit var adapter: SmsAdapter
    private lateinit var currentTimeTextView: TextView
    private lateinit var fromAddressTextView: TextView
    private var deletedSmsCount = 0 // Track the count of deleted SMS

    // Register the permission request callback
    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                loadSms()
            } else {
                Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
            }
        }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize UI components
        currentTimeTextView = findViewById(R.id.currentTimeTextView)
        fromAddressTextView = findViewById(R.id.fromAddressTextView)
//        val viewDeletedSmsButton = findViewById<ImageButton>(R.id.viewDeletedSmsButton)
        // 获取自定义布局中的 TextView 并设置角标数量
        val viewDeletedSmsButtonWithBadge = findViewById<View>(R.id.viewDeletedSmsButtonWithBadge)
        val imageButton = viewDeletedSmsButtonWithBadge.findViewById<ImageButton>(R.id.imageButton)
        // Initialize deletedSmsCount based on the current state of deletedSmsList
        deletedSmsCount = deletedSmsList.size
        updateDeletedSmsBadge()

        // 设置 RecyclerView 适配器等其他逻辑
        // ...

        // 示例:设置点击事件
//        backButton.setOnClickListener {
//            // 返回上一个活动或处理返回逻辑
//        }

//        applyButton.setOnClickListener {
//            // 处理应用按钮点击事件
//        }

        imageButton.setOnClickListener {
            // 处理查看已删除短信按钮点击事件
            startDeletedSmsActivity()
        }
//        viewDeletedSmsButton.setOnClickListener {
//            startDeletedSmsActivity()
//        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
            requestPermissionLauncher.launch(Manifest.permission.READ_SMS)
        } else {
            loadSms()
        }
    }
    private fun updateDeletedSmsBadge() {
        val viewDeletedSmsButtonWithBadge = findViewById<View>(R.id.viewDeletedSmsButtonWithBadge)
        val badgeTextView = viewDeletedSmsButtonWithBadge.findViewById<TextView>(R.id.badgeTextView)
        if (deletedSmsCount > 0) {
            badgeTextView.visibility = View.VISIBLE
            badgeTextView.text = String.format(Locale.getDefault(), "%d", deletedSmsCount)
        } else {
            badgeTextView.visibility = View.GONE
        }
    }

    private fun loadSms() {
        smsList = mutableListOf()


        val cursor: Cursor? = contentResolver.query(
            Telephony.Sms.CONTENT_URI,
            null,
            null,
            null,
            Telephony.Sms.DEFAULT_SORT_ORDER
        )

        try {
            cursor?.let {
                while (it.moveToNext()) {
                    val address = it.getString(it.getColumnIndexOrThrow(Telephony.Sms.ADDRESS))
                    val body = it.getString(it.getColumnIndexOrThrow(Telephony.Sms.BODY))
                    val receivedTimeMillis = it.getLong(it.getColumnIndexOrThrow(Telephony.Sms.DATE))
                    val receivedTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss ", Locale.getDefault()).format(receivedTimeMillis)
                    smsList.add(SmsItem(address, body, receivedTime))
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "Failed to read SMS", Toast.LENGTH_SHORT).show()
        } finally {
            cursor?.close()
        }

        setupRecyclerView()
    }

    private fun setupRecyclerView() {
        val recyclerView = findViewById <RecyclerView>(R.id.recyclerView)
        adapter = SmsAdapter(smsList) { selectedSms ->
            updateCurrentTime(selectedSms.receivedTime, selectedSms.address)
        }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        // Attach PagerSnapHelper to ensure snapping to items
        val snapHelper = PagerSnapHelper()
        snapHelper.attachToRecyclerView(recyclerView)

        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val firstVisiblePosition = findCenterViewPosition(layoutManager, snapHelper)

                if (firstVisiblePosition != RecyclerView.NO_POSITION) {
                    val selectedSms = smsList [firstVisiblePosition]
                    updateCurrentTime(selectedSms.receivedTime, selectedSms.address)
                }
            }
        })


        val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
            0,
            ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP
        ) {
            override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                return false
            }
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val position = viewHolder.bindingAdapterPosition
                Log.d("MainActivity", " Swiped position: $position ")
                if (position == RecyclerView.NO_POSITION) {
                    Toast.makeText(this@MainActivity, "Invalid position", Toast.LENGTH_SHORT).show()
                    Log.e("MainActivity", "Invalid position")
                    return
                }

                when (direction) {
                    ItemTouchHelper.UP -> {
                        // Move to trash
                        val removedSms = smsList [position]
                        deletedSmsList.add(removedSms) // Add to deleted list
                        deletedSmsCount++
                        smsList.removeAt(position)
                        adapter.notifyItemRemoved(position)
                        adapter.notifyItemRangeChanged(position, smsList.size - position)
                        Toast.makeText(this@MainActivity, "SMS Moved to Trash", Toast.LENGTH_SHORT).show()
                        Log.d("MainActivity", "SMS Moved to Trash: ${removedSms.address} at position $ position")
                        updateDeletedSmsBadge() // Update the UI
                    }

//            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//                val position = viewHolder.bindingAdapterPosition
//                if (position == RecyclerView.NO_POSITION) return
//
//                when (direction) {
//                    ItemTouchHelper.UP -> {
//                        // Move to trash
//                        val removedSms = smsList [position]
//                        deletedSmsList.add(removedSms) // Add to deleted list
//                        smsList.removeAt(position)
//                        adapter.notifyItemRemoved(position)
//                        Toast.makeText(this@MainActivity, "SMS Moved to Trash", Toast.LENGTH_SHORT).show()
//                    }
                    ItemTouchHelper.LEFT, ItemTouchHelper.RIGHT -> {
                        // Left/Right swipe to change message
                        adapter.notifyItemChanged(position)
                    }
                }
            }
        }

        val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
        itemTouchHelper.attachToRecyclerView(recyclerView)
    }
    private fun findCenterViewPosition(layoutManager: LinearLayoutManager, snapHelper: PagerSnapHelper): Int {
        val centerView = snapHelper.findSnapView(layoutManager)
        return centerView?.let { layoutManager.getPosition(it) } ?: RecyclerView.NO_POSITION
    }
    private fun updateCurrentTime(receivedTime: String, address: String) {
        currentTimeTextView.text = getString(R.string.sms_received_time_label, receivedTime)
        fromAddressTextView.text = getString(R.string.sms_from_address_label, address)
    }
    private fun startDeletedSmsActivity() {
        val intent = Intent(this, DeletedSmsActivity:: class.java)
        intent.putExtra("DELETED_SMS_LIST", ArrayList(deletedSmsList))
        startActivity(intent)
    }
}

SmsAdapter

package com.example.smsslide

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class SmsAdapter(private val smsList: List <SmsItem>, private val onSelect: (SmsItem) -> Unit) : RecyclerView.Adapter <SmsAdapter.SmsViewHolder>() {

    class SmsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//        val textView: TextView = itemView.findViewById(R.id.textView)
        val addressTextView: TextView = itemView.findViewById(R.id.addressTextView)
        val bodyTextView: TextView = itemView.findViewById(R.id.bodyTextView)
        val receivedTimeTextView: TextView = itemView.findViewById(R.id.receivedTimeTextView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SmsViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sms, parent, false)
        return SmsViewHolder(view)
    }

    override fun onBindViewHolder(holder: SmsViewHolder, position: Int) {
        val smsItem = smsList [position]
        holder.addressTextView.text = smsItem.address
        holder.bodyTextView.text = smsItem.body
        holder.receivedTimeTextView.text = smsItem.receivedTime
//        val details = holder.itemView.context.getString(
//            R.string.sms_details_format,
//            smsItem.body
//        )
//        holder.textView.text = details

        // Set click listener to update current time and from address in MainActivity
        holder.itemView.setOnClickListener {
            onSelect(smsItem)
        }
    }

    override fun getItemCount(): Int {
        return smsList.size
    }
}

SmsItem

package com.example.smsslide

import java.io.Serializable

data class SmsItem(val address: String, val body: String, val receivedTime: String) : Serializable

DeletedSmsActivity

package com.example.smsslide

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class DeletedSmsActivity : AppCompatActivity() {

    private lateinit var deletedSmsList: MutableList <SmsItem>
    private lateinit var adapter: SmsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_deleted_sms)

        // Retrieve the deleted SMS list from MainActivity
        @Suppress("UNCHECKED_CAST")
        deletedSmsList = intent.getSerializableExtra("DELETED_SMS_LIST") as MutableList <SmsItem>

        setupRecyclerView()
    }

    private fun setupRecyclerView() {
        val recyclerView = findViewById <RecyclerView>(R.id.deletedSmsRecyclerView)
        adapter = SmsAdapter(deletedSmsList) { selectedSms ->
            // Handle click event if needed
        }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
    }
}

res文件夹

layout文件

  1. activity_deleted_sms.xml

    <?xml version="1.0" encoding="utf-8"?>
    < RelativeLayout xmlns: android = "http://schemas.android.com/apk/res/android"
        android: layout_width = "match_parent"
        android: layout_height = "match_parent" >
    
        < TextView
            android: id = "@+id/deletedSmsTitle"
            android: layout_width = "wrap_content"
            android: layout_height = "wrap_content"
            android: text = "@string/recently_deleted_sms"
            android: textSize = "24sp"
            android: layout_marginTop = "16dp"
            android: layout_centerHorizontal = "true"/>
    
        < androidx.recyclerview.widget.RecyclerView
            android: id = "@+id/deletedSmsRecyclerView"
            android: layout_width = "match_parent"
            android: layout_height = "match_parent"
            android: layout_below = "@id/deletedSmsTitle"
            android: layout_weight = "1"
            android: layout_marginTop = "16dp"
            android: layout_marginBottom = "16dp"/>
    
    </RelativeLayout>
    
  2. activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    < LinearLayout xmlns: android = "http://schemas.android.com/apk/res/android"
        android: layout_width = "match_parent"
        android: layout_height = "match_parent"
        android: orientation = "vertical"
        android: padding = "16dp" >
    
        <!-- First row: Back button, Current Time, Trash button -->
        < LinearLayout
            android: layout_width = "match_parent"
            android: layout_height = "wrap_content"
            android: orientation = "horizontal"
            android: gravity = "center_vertical" >
    
            < ImageButton
                android: id = "@+id/backButton"
                android: layout_width = "wrap_content"
                android: layout_height = "wrap_content"
                android: src = "@drawable/baseline_arrow_back_24"
                android: contentDescription = "@string/back"/>
    
            < LinearLayout
                android: layout_width = "0dp"
                android: layout_height = "wrap_content"
                android: orientation = "vertical"
                android: gravity = "center_horizontal"
                android: layout_weight = "1" >
    
                < TextView
                    android: id = "@+id/currentTimeTextView"
                    android: layout_width = "wrap_content"
                    android: layout_height = "wrap_content"
                    android: text = ""
                    android: textSize = "18sp"
                    android: gravity = "center_vertical"/>
    
                < TextView
                    android: id = "@+id/fromAddressTextView"
                    android: layout_width = "wrap_content"
                    android: layout_height = "wrap_content"
                    android: text = ""
                    android: textSize = "18sp"
                    android: gravity = "center_vertical"/>
    
            </LinearLayout>
    
            < include layout = "@layout/trash_image_button_with_badge"
                android: id = "@+id/viewDeletedSmsButtonWithBadge"/>
    
        </LinearLayout>
    
        <!-- Second row: Large block for SMS content -->
        < androidx.recyclerview.widget.RecyclerView
            android: id = "@+id/recyclerView"
            android: layout_width = "match_parent"
            android: layout_height = "0dp"
            android: layout_weight = "1"
            android: layout_marginTop = "16dp"
            android: layout_marginBottom = "16dp"/>
    
        <!-- Third row: Apply button -->
        < Button
            android: id = "@+id/applyButton"
            android: layout_width = "match_parent"
            android: layout_height = "wrap_content"
            android: text = "@string/apply"
            android: textSize = "18sp"/>
    </LinearLayout>
    
  3. item_sms.xml

    <?xml version="1.0" encoding="utf-8"?>
    < androidx.cardview.widget.CardView 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 = "wrap_content"
        android: layout_marginBottom = "16dp"
        app: cardCornerRadius = "8dp"
        app: cardElevation = "4dp" >
    
        < LinearLayout
            android: layout_width = "match_parent"
            android: layout_height = "wrap_content"
            android: orientation = "vertical"
            android: padding = "16dp" >
    
            < TextView
                android: id = "@+id/addressTextView"
                android: layout_width = "wrap_content"
                android: layout_height = "wrap_content"
                android: text = "@string/address"
                android: textSize = "18sp"
                android: textStyle = "bold"/>
    
            < TextView
                android: id = "@+id/bodyTextView"
                android: layout_width = "wrap_content"
                android: layout_height = "wrap_content"
                android: text = "@string/body"
                android: textSize = "16sp"
                android: layout_marginTop = "4dp"/>
    
            < TextView
                android: id = "@+id/receivedTimeTextView"
                android: layout_width = "wrap_content"
                android: layout_height = "wrap_content"
                android: text = "@string/received_time"
                android: textSize = "14sp"
                android: layout_marginTop = "4dp"
                android: textColor = "#757575"/>
        </LinearLayout>
    </androidx.cardview.widget.CardView>
    
    
    
    
  4. trash_image_button_with_badge.xml

    <?xml version="1.0" encoding="utf-8"?>
    < FrameLayout xmlns: android = "http://schemas.android.com/apk/res/android"
        android: layout_width = "wrap_content"
        android: layout_height = "wrap_content" >
    
        < ImageButton
            android: id = "@+id/imageButton"
            android: layout_width = "wrap_content"
            android: layout_height = "wrap_content"
            android: src = "@drawable/baseline_delete_24"
            android: contentDescription = "@string/trash"/>
    
        < TextView
            android: id = "@+id/badgeTextView"
            android: layout_width = "wrap_content"
            android: layout_height = "wrap_content"
            android: text = "0"
            android: textSize = "12sp"
            android: textColor = "#FFFFFF"
            android: background = "@drawable/red_circle_background"
            android: paddingStart = "6dp"
            android: paddingEnd = "6dp"
            android: gravity = "center"
            android: layout_gravity = "top|end"
            android: visibility = "gone"/>
    </FrameLayout>