Android開發筆記[10]-關於頁

qsBye發表於2024-03-17

摘要

構建關於頁、終端使用者許可頁(EULA)頁和隱私協議頁;Compose頁面中嵌入xml佈局;Compose頁面中新增markdown文字.

關鍵資訊

  • Android Studio:Iguana | 2023.2.1
  • Gradle:distributionUrl=https://services.gradle.org/distributions/gradle-8.4-bin.zip
  • jvmTarget = '1.8'
  • minSdk 21
  • targetSdk 34
  • compileSdk 34
  • 開發語言:Kotlin,Java
  • ndkVersion = '21.1.6352462'
  • kotlin版本:1.9.20
  • kotlinCompilerExtensionVersion '1.5.4'
  • com.android.library:8.3

原理簡介

EULA(終端使用者許可協議)

[https://blog.csdn.net/LingLing1301/article/details/124561891]
終端使用者許可協議(End User Licence Agreement,EULA),指的是一家公司的軟體與軟體的使用者所達成的協議,此協議一般在軟體安裝時出現。如果使用者拒絕接受這家公司的EULA,那麼便不能安裝此軟體。終端使用者許可協議是軟體應用程式作者或者釋出者與應用程式使用者之間的合法合同。終端使用者許可協議(EULA),通常是指“軟體許可”,它與租賃協議類似;使用者同意支付軟體的使用費用,並且向軟體作者或者發行者承諾遵守EULA中規定的所有約束條件。使用者被告知,當他們開啟軟體包的包裝、開啟CD盒的封條、將卡片寄回給軟體發行者、安裝應用程式、執行下載檔案或者簡單的使用應用程式的時候就意味著他們已經“接受”了EULA中的條款。使用者可以寄回軟體產品或者在安裝過程中當EULA提示接受按鈕的時候點選“我不接受”來拒絕這個協議。

Markwon簡介

[https://github.com/noties/Markwon]
[https://noties.io/Markwon/]
Markwon is a markdown library for Android. It parses markdown following commonmark-spec with the help of amazing commonmark-java library and renders result as Android-native Spannables. No HTML is involved as an intermediate step. No WebView is required. It's extremely fast, feature-rich and extensible.
It gives ability to display markdown in all TextView widgets (TextView, Button, Switch, CheckBox, etc), Toasts and all other places that accept Spanned content. Library provides reasonable defaults to display style of a markdown content but also gives all the means to tweak the appearance if desired. All markdown features listed in commonmark-spec are supported (including support for inlined/block HTML code, markdown tables, images and syntax highlight).
Markwon comes with a sample application. It is a collection of library usages that comes with search and source code for each code sample.
Since version 4.2.0 Markwon comes with an editor to highlight markdown input as user types (for example in EditText).
Markwon 是一個用於 Android 的 Markdown 庫。它使用令人驚歎的 commonmark-java 庫解析遵循 CommonMark 規範的 Markdown,並將結果渲染為 Android 本地的 Spannables。在此過程中不涉及 HTML 作為中間步驟,也不需要 WebView。它非常快速、功能豐富且可擴充套件。
Markwon 能夠使所有 TextView 小部件(TextView、Button、Switch、CheckBox 等)顯示 Markdown,以及 Toasts 和所有接受 Spanned 內容的其他地方。庫提供了合理的預設樣式來顯示 Markdown 內容,但如果需要,也提供了所有方法來調整外觀。Markwon 支援 CommonMark 規範中列出的所有 markdown 特性(包括對內聯/塊級 HTML 程式碼、Markdown 表格、圖片和語法高亮的支援)。
Markwon 附帶一個示例應用程式。這是一個包含庫用法的集合,每個程式碼示例都帶有搜尋和原始碼。
自版本 4.2.0 起,Markwon 附帶了一個編輯器,用於在使用者輸入時高亮顯示 Markdown(例如在 EditText 中)。

「關於」頁面

[https://juejin.cn/post/7139019841284866055]
關於頁Compose模板
APP的「關於」頁面通常包含一些基礎的資訊,如APP的名稱、版本號、開發者資訊、更新日誌、隱私政策、使用者協議等內容。這個頁面主要是為了向使用者提供一些關於APP的基本資訊和說明。
具體來說,「關於」頁面可能包含以下幾個部分:

  1. APP名稱:顯示APP的全稱,有時也會配合APP的Logo一起展示。
  2. 版本資訊:顯示當前的APP版本號,有時候還會顯示此版本的更新日期。
  3. 開發者資訊:顯示開發者的名稱、地址、聯絡方式等資訊,有時候也會提供開發者的官方網站連結。
  4. 更新日誌:列出最近幾次版本更新的內容,幫助使用者瞭解APP的新功能和改進。
  5. 隱私政策:解釋APP如何處理使用者的個人資訊,以及如何保護使用者的隱私。
  6. 使用者協議:列出使用者在使用APP時需要遵守的條款和條件。
    以上這些資訊可以幫助使用者更好地理解和使用APP,同時也能讓使用者感到更加安全和信任。

app的隱私政策

[http://app.gjzwfw.gov.cn/jmopen/webapp/html5/yhysxy/index.html]
[https://zhuanlan.zhihu.com/p/670072425]
APP隱私合規現狀與防範措施
[https://www.goupsec.com/news/5460.html]
2021年11月1《個人資訊保護法》正式施行,標誌資訊保護進入強監管時代,同時,APP監管也被提升到前所未有的高度,資料安全、使用者隱私、甚至功能體驗等各個方面都出臺了相應的規則規範,監管的初衷是:從各個層面保障使用者的權益,避免使用者的隱私、體驗、資料被濫用,甚至威脅國家安全,一旦違規被查處面臨的懲罰是非常嚴厲的,因此產品運營方必須高度重視。一方面,在產品設計、開發階段就要充分考慮並滿足各種監管要求;另一方面,一旦查出隱患問題,要積極響應,及時整改,否則可能面臨工信部通報,甚至全面下架風險,首當其衝的一塊是:APP隱私合規。
APP隱私合規指簡單說就是:使用者隱私的收集、儲存、使用、加工、傳輸、提供、公開、刪除等都要合乎法規個人資訊保護法,遵守原則。從APP端來看,它關係到使用者的切身體驗,使用者自身也能直觀感受到自己的隱私是否被過分索取。比如,申請與自身功能毫不相關的許可權,不給還拒絕提供服務等場景。
正常的開發是在APP啟動之初就要初始化很多環境、引數,而這些初始化很可能涉及一些隱私API的呼叫,在隱私合規的框架下,這些呼叫只能被人為延後,或者取消。
示例文字:

本軟體尊重並保護所有使用服務使用者的個人隱私權。為了給您提供更準確、更有個性化的服務,本軟體會按照本隱私權政策的規定使用和披露您的個人資訊。但本軟體將以高度的勤勉、審慎義務對待這些資訊。除本隱私權政策另有規定外,在未徵得您事先許可的情況下,本軟體不會將這些資訊對外披露或向第三方提供。本軟體會不時更新本隱私權政策。您在同意本軟體服務使用協議之時,即視為您已經同意本隱私權政策全部內容。本隱私權政策屬於本軟體服務使用協議不可分割的一部分。
1.適用範圍
a)在您使用本軟體網路服務,本軟體自動接收並記錄的您的手機上的資訊,包括但不限於您的健康資料、使用的語言、訪問日期和時間、軟硬體特徵資訊及您需求的網頁記錄等資料;
2.資訊的使用
a)在獲得您的資料之後,本軟體會將其上傳至伺服器,以生成您的排行榜資料,以便您能夠更好地使用服務。
3.資訊披露
a)本軟體不會將您的資訊披露給不受信任的第三方。
b)根據法律的有關規定,或者行政或司法機構的要求,向第三方或者行政、司法機構披露;
c)如您出現違反中國有關法律、法規或者相關規則的情況,需要向第三方披露;
4.資訊儲存和交換
本軟體收集的有關您的資訊和資料將儲存在本軟體及(或)其關聯公司的伺服器上,這些資訊和資料可能傳送至您所在國家、地區或本軟體收集資訊和資料所在地的境外並在境外被訪問、儲存和展示。
5.資訊保安
a)在使用本軟體網路服務進行網上交易時,您不可避免的要向交易對方或潛在的交易對方披露自己的個人資訊,如聯絡方式或者郵政地址。請您妥善保護自己的個人資訊,僅在必要的情形下向他人提供。如您發現自己的個人資訊洩密,請您立即聯絡本軟體客服,以便本軟體採取相應措施。

Compose中嵌入xml佈局

[https://blog.csdn.net/u010436867/article/details/120759187]

class ComposeAndXmlActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeContent()
        }
    }

    @SuppressLint("SetTextI18n")
    @Composable
    fun ComposeContent() {
        LazyColumn(modifier = Modifier.fillMaxWidth(), content = {
            item {
                Text(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(100.dp)
                        .background(color = Color.Green),
                    textAlign = TextAlign.Center,
                    text = "Compose 部分-頭部",
                )    

                /* start XML佈區域性分 */
                AndroidView(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(8.dp),
                    factory = { ctx ->
                        val view =
                            LayoutInflater.from(ctx).inflate(R.layout.activity_compose_xml, null)
                        view.findViewById<TextView>(R.id.fromXML).text = "Xml佈區域性分"
                        view
                    },
                )
                /* end XML佈區域性分 */

            }
            item {
                Text(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(100.dp)
                        .padding(8.dp)
                        .background(color = Color.Yellow),
                    textAlign = TextAlign.Center,
                    text = "Compose 部分-尾部",
                )
            }
        })
    }
}

xml佈局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/fromXML"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/black"
        android:gravity="center"
        android:text="..."
        android:textColor="@color/white"
        android:textSize="12sp" />
</androidx.appcompat.widget.LinearLayoutCompat>

實現

  1. 配置gradle
    build.gradle
/* start markdown相關 */
implementation "io.noties.markwon:core:4.6.2"
implementation 'io.noties.markwon:image:4.6.2'
implementation 'io.noties.markwon:image-glide:4.6.2'
/* end markdown相關 */
  1. 核心程式碼
    AboutActivity.kt
package cn.qsbye.alittlesmile_android

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Star
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavHostController
import cn.qsbye.alittlesmile_android.AboutPageClass.AboutScreen
import cn.qsbye.alittlesmile_android.AboutPageClass.BaseTextItem
import cn.qsbye.alittlesmile_android.AboutPageClass.LinkText
import cn.qsbye.alittlesmile_android.AboutPageClass.ModuleItem
import cn.qsbye.alittlesmile_android.AboutPageClass.MyViewModel
import cn.qsbye.alittlesmile_android.AboutPageClass.NormalSubItemWithStartIconData
import cn.qsbye.alittlesmile_android.AboutPageClass.NormalWithStartIconSubItemModule
import cn.qsbye.alittlesmile_android.AboutPageClass.RightsTextItem
import cn.qsbye.alittlesmile_android.AboutPageClass.boxSampleIcon
import cn.qsbye.alittlesmile_android.ui.theme.Alittlesmile_androidTheme
import io.noties.markwon.Markwon

class AboutActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Alittlesmile_androidTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // 顯示關於頁內容
                    MyAboutPage(MyViewModel(),this@AboutActivity) // 這裡假設你有一個 MyViewModel 例項
                }
            }
        }
    }
}

/* start 繼承於關於頁模板AboutPageClass */
// 新增context作為上下文引數傳入
@Composable
fun MyAboutPage(myViewModel: MyViewModel, context_input: AboutActivity) {
    val list = remember {
        listOf(
            NormalSubItemWithStartIconData("給我好評👍", subText = "你的好評將會給予我們莫大的動力與幫助", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("分享應用", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("反饋&建議", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("獲取幫助💁", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("更新日誌📝", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("版本狀態",
                subText = context_input.resources.getString(R.string.app_version),
                startIcon = boxSampleIcon)
        ) // end ListOf
    }

    Column(
        Modifier
            .fillMaxSize()
            .background(Color.White)) {
        AboutScreen(
            topContent = {
                Column(
                    modifier = Modifier.background(
                        brush = Brush.verticalGradient(
                            listOf(
                                Color(0xFF89B6A2),
                                Color.White
                            )
                        )
                    )
                ) {
                    ModuleItem(
                        modifier = Modifier
                            .padding(12.dp)
                            .background(Color.White)) {
                        Column(modifier = Modifier.padding(bottom = 16.dp)) {
                            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                                Icon(painter = painterResource(id = R.drawable.ic_launcher_foreground), contentDescription = "logo")
                            }
                            BaseTextItem {
                                Text(
                                    text = "關於「曉曉愛學」",
                                    Modifier.padding(vertical = 6.dp),
                                    style = MaterialTheme.typography.titleLarge
                                )

                                /* start markdown顯示 */
                                AndroidView(
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .padding(8.dp),
                                    factory = { ctx ->
                                        val layout_from_xml =
                                            LayoutInflater.from(ctx).inflate(R.layout.about_activity, null)
                                        val textview_from_xml = layout_from_xml.findViewById<TextView>(R.id.about_activity_textview) as TextView

                                        val markwon = Markwon.create(context_input)
                                        // render raw input to styled markdown
                                        // Set the styled markdown to the TextView
                                        markwon.setMarkdown(textview_from_xml, about_markdown_text)
                                        layout_from_xml // Return the TextView instance
                                    },
                                )
                                /* end markdown顯示 */

                            }
                            Row(
                                Modifier
                                    .fillMaxWidth()
                                    .padding(top = 8.dp), horizontalArrangement = Arrangement.SpaceBetween) {
                                Row {
                                    Icon(
                                        imageVector = Icons.Outlined.Star,
                                        contentDescription = "star",
                                        modifier = Modifier.padding(start = 16.dp, end=12.dp),
                                        tint = Color.Cyan
                                    )

                                    Icon(
                                        imageVector = Icons.Outlined.Star,
                                        contentDescription = "star",
                                        tint = Color.Magenta
                                    )
                                }
                                LinkText(
                                    text = "第三方軟體清單",
                                    modifier = Modifier.padding(end = 16.dp),
                                    color = Color(0xFF89B6A2)
                                ) {
                                    // onClick事件

                                }
                            }
                        }
                    }
                }
            },
            mainContent = {
                NormalWithStartIconSubItemModule(
                    itemList = list,
                    modifier = Modifier.padding(12.dp),
                    backGroundColor = Color.White,
                    showDivider = false,
                    elevation = 2.dp,
                    itemPadding = 24.dp,
                    textStyle = MaterialTheme.typography.titleLarge.copy(color = Color(0xFF404040)),
                    subTextStyle = MaterialTheme.typography.bodyMedium.copy(color = Color(0xFF797979)),
                    cardShape = RoundedCornerShape(12.dp), // 定義模組框的形狀,這裡使用圓角,且圓角大小為 12 dp
                    extraContent = {
                        Row(
                            Modifier
                                .fillMaxWidth()
                                .padding(top = 8.dp), horizontalArrangement = Arrangement.End) {
                            Row(Modifier.fillMaxWidth(0.5f), horizontalArrangement = Arrangement.SpaceEvenly) {
                                LinkText(text = "隱私政策", color = Color(0xFF89B6A2)){
                                    // onClick事件
                                    // 跳轉頁面
                                    val intent = Intent(context_input, PrivacyPolicyActivity::class.java)
                                    context_input.startActivity(intent) // 使用正確的上下文來啟動活動
                                }
                                LinkText(text = "使用者協議", color = Color(0xFF89B6A2)) {
                                    // onClick事件
                                    Toast.makeText(
                                        context_input,
                                        "使用者協議",
                                        Toast.LENGTH_SHORT
                                    ).show()
                                    // 跳轉頁面
                                    val intent = Intent(context_input, EulaActivity::class.java)
                                    context_input.startActivity(intent) // 使用正確的上下文來啟動活動
                                }
                                LinkText(text = "證照資訊", color = Color(0xFF89B6A2)) {
                                    // onClick事件

                                }
                            }
                        }
                    },
                    onClick = { index ->
                        if (index == 4/* 更新日誌項的索引 */) {
                            // 使用 Compose 的 NavController
                            //NavHostController.navigate(ChangelogActivity::class.java)

                            // 跳轉頁面
                            val intent = Intent(context_input, ChangelogActivity::class.java)
                            context_input.startActivity(intent) // 使用正確的上下文來啟動活動
                            // finish()
                        }
                    } // end onClick
                )
            },
            bottomContent = {
                BaseTextItem {
                    Text(
                        text = "「曉曉愛學」所有貢獻者 版權所有",
                        modifier = Modifier.padding(bottom = 16.dp, start = 8.dp),
                        color = Color(0xFF797979)
                    )
                    RightsTextItem(
                        dateText = "2021-2024",
                        name = "all contributors of _alittlesmile_",
                        style = LocalTextStyle.current.copy(color = Color(0xFF797979))
                    )
                }
            }
        )
    }
}
/* end 繼承於關於頁模板AboutPageClass */

/* start 關於資訊 markdown */
val about_markdown_text = """
    ### 「曉曉愛學」是一款幫助你更好學習的APP。具體功能包括:
    ### 省略
""".trimIndent()
/* end 關於資訊 markdown */

PrivacyPolicyActivity.kt

package cn.qsbye.alittlesmile_android
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import cn.qsbye.alittlesmile_android.MyEulaPage
import cn.qsbye.alittlesmile_android.R
import cn.qsbye.alittlesmile_android.eula_markdown_text
import cn.qsbye.alittlesmile_android.ui.theme.Alittlesmile_androidTheme
import io.noties.markwon.Markwon

class PrivacyPolicyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Alittlesmile_androidTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // 顯示隱私政策頁內容
                    MyPrivacyPolicyPage(context_input = this@PrivacyPolicyActivity)
                }
            }
        }
    }
}

/* start PrivacyPolicy頁面佈局 */
@Composable
fun MyPrivacyPolicyPage(context_input: Activity, modifier: Modifier = Modifier) {
    /* start markdown顯示 */
    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp),
        factory = { ctx ->
            val layout_from_xml =
                LayoutInflater.from(ctx).inflate(R.layout.about_activity, null)
            val textview_from_xml = layout_from_xml.findViewById<TextView>(R.id.about_activity_textview) as TextView

            val markwon = Markwon.create(context_input)
            // render raw input to styled markdown
            // Set the styled markdown to the TextView
            markwon.setMarkdown(textview_from_xml, privacy_policy_markdown_text)
            layout_from_xml // Return the TextView instance
        },
    )
    /* end markdown顯示 */

}
/* end PrivacyPolicy頁面佈局 */

/* start 隱私政策 markdown */
val privacy_policy_markdown_text = """
    ## 隱私政策
    - 更新日期:2024年3月16日
    
    「曉曉愛學」所有貢獻者(簡稱“我們”)作為「曉曉愛學」的運營者,深知個人資訊的重要性,我們將按照法律法規的規定,保護您的個人資訊及隱私安全。我們制定本“隱私政策”並特別提示:希望您在使用「曉曉愛學」(以下簡稱"本應用")服務前仔細閱讀並理解本隱私政策,以便做出適當選擇。

    0. 所需許可權
    請您仔細閱讀以下許可權說明為提供某些服務,我們需要向您獲取以下許可權
    * (1)讀取/修改手機儲存許可權,用於儲存下載檔案(圖片/影片/音訊)。
    * (2)讀取/修改相簿、音訊許可權,用於選取手機中的圖片/影片/音訊進行編輯,編輯完成後儲存到手機。 
    * (3)剪下板許可權,用於檢測下載的任務,減少互動步驟。 
    * (4)讀取安裝應用列表,用於三方登入、支付前判斷是否安裝相關客戶端,避免程式錯誤。
    * (5)讀取裝置識別碼,裝置MAC地址,用於確定裝置唯一性,用於友盟統計,Bugly錯誤日誌上傳定位。

    1. 我們如何收集和使用您的資訊
    * 1.1 登入 
    您可以使用第三方賬號(微信)登入並使用本應用,您將授權我們獲取您在第三方平臺註冊的公開資訊(頭像,暱稱),用於與本應用賬號繫結,使您可以直接登入並使用本產品和相關服務。
    * 1.2 提供商品或服務
    我們希望為您提供優質而可靠的服務,為實現安全保障功能所收集的資訊是必要資訊。
    * 1.2.1 裝置資訊與日誌資訊
    > a.為了保障軟體與服務的安全執行,我們會收集您的硬體型號、作業系統版本號、國際移動裝置識別碼、唯一裝置識別符號、網路裝置硬體地址、IP 地址、WLAN接入點、藍芽、基站、軟體版本號、網路接入方式、型別、狀態、網路質量資料、操作、使用、服務日誌。 
    > b.為了預防惡意程式及安全運營所必需,我們會收集安裝的應用資訊或正在執行的程序資訊、應用程式的總體執行、使用情況與頻率、應用崩潰情況、總體安裝使用情況、效能資料、應用來源。
    > c.我們可能使用您的賬戶資訊、裝置資訊、服務日誌資訊以及我們關聯方、合作方在獲得您授權或依法可以共享的資訊,用於判斷賬戶安全、進行身份驗證、檢測及防範安全事件。
    * 1.3 收集、使用個人資訊目的變更
    請您瞭解,隨著我們業務的發展,可能會對本應用的功能和提供的服務有所調整變化。原則上,當新功能或服務與我們當前提供的功能或服務相關時,收集與使用的個人資訊將與原處理目的具有直接或合理關聯。在與原處理目的無直接或合理關聯的場景下,我們收集、使用您的個人資訊,會再次進行告知,並徵得您的同意。
    * 1.4 依法豁免徵得同意收集和使用的個人資訊
    請您理解,在下列情形中,根據法律法規及相關國家標準,我們收集和使用您的個人資訊無需徵得您的授權同意:
    > a.與國家安全、國防安全直接相關的;
    > b.與公共安全、公共衛生、重大公共利益直接相關的;
    > c.與犯罪偵查、起訴、審判和判決執行等直接相關的;
    > d.出於維護個人資訊主體或其他個人的生命、財產等重大合法權益但又很難得到本人同意的;
    > e.所收集的您的個人資訊是您自行向社會公眾公開的;
    > f.從合法公開披露的資訊中收集的您的個人資訊的,如合法的新聞報導、政府資訊公開等渠道;
    > g.根據您的要求籤訂或履行合同所必需的;
    > h.用於維護軟體及相關服務的安全穩定執行所必需的,例如發現、處置軟體及相關服務的故障;
    > i.為合法的新聞報導所必需的;
    > j.學術研究機構基於公共利益開展統計或學術研究所必要,且對外提供學術研究或描述的結果時,對結果中所包含的個人資訊進行去標識化處理的;
    > k.法律法規規定的其他情形

    2. 我們如何使用Cookie等同類技術

    * 2.1 在您未拒絕接受cookies的情況下,本應用會在您的計算機上設定或取用cookies ,以便您能登入或使用依賴於cookies的本應用平臺服務或功能。
    * 2.2 您有權選擇接受或拒絕接受cookies。您可以透過修改瀏覽器設定的方式拒絕接受cookies。但如果您選擇拒絕接受cookies,則您可能無法登入或使用依賴於cookies的本應用網路服務或功能。
    * 2.3透過本應用所設cookies所取得的有關資訊,將適用本政策。

    3. 我們如何共享、轉讓、公開披露個人資訊
    * 3.1共享
    * 3.1.1 共享原則
    > a.授權同意原則: 未經您的同意,我們不會共享您的個人資訊,除非共享的個人資訊是去標識化處理後的資訊,且共享第三方無法重新識別此類資訊的自然人主體。如果第三方使用資訊的目的超越原授權同意範圍,他們需要重新徵得您的同意。
    > b.安全審慎原則: 我們將審慎評估第三方使用共享資訊的目的,對這些合作方的安全保障能力進行綜合評估,並要求其遵循合作法律協議。我們會對合作方獲取資訊的軟體工具開發包(SDK)、應用程式介面(API)進行嚴格的安全監測,以保護資料安全。
    * 3.1.2 實現功能或服務的共享資訊
    支付功能:支付功能由與我們合作的第三方支付機構向您提供服務。第三方支付機構可能需要收集您的姓名、銀行卡型別及卡號、有效期及手機號碼。銀行卡號、有效期及手機號碼 是個人敏感資訊,這些資訊是支付功能所必需的資訊,拒絕提供將導致您無法使用該功能,但不影響其他功能的正常使用。
    * 3.2 轉讓
    我們不會轉讓您的個人資訊給任何其他第三方,除非徵得您的明確同意。
    * 3.3 公開展示
    我們不會公開披露您的資訊,除非遵循國家法律法規規定或者獲得您的同意。我們公開披露您的個人資訊會採用符合行業內標準的安全保護措施。
    * 3.4 依法豁免徵得同意共享、轉讓、公開披露的個人資訊
    請您理解,在下列情形中,根據法律法規及國家標準,我們共享、轉讓、公開披露您的個人資訊無需徵得您的授權同意:
    > a.與國家安全、國防安全直接相關的;
    > b.與公共安全、公共衛生、重大公共利益直接相關的;
    > c.與犯罪偵查、起訴、審判和判決執行等直接相關的;
    > d.出於維護您或其他個人的生命、財產等重大合法權益但又很難得到本人同意的;
    > e.您自行向社會公眾公開的個人資訊;
    > f.從合法公開披露的資訊中收集個人資訊的,如合法的新聞報導、政府資訊公開等渠道。
    4. 我們如何儲存個人資訊

    * 4.1 儲存地點
    我們依照法律法規的規定,將在境內運營過程中收集和產生的您的個人資訊儲存於中華人民共和國境內。目前,我們不會將上述資訊傳輸至境外,如果我們向境外傳輸,我們將會遵循相關國家規定或者徵求您的同意。
    * 4.2 儲存期限
    我們僅在為提供便捷下載及服務之目的所必需的期間內保留您的授權登入資訊

    5. 您如何管理您的個人資訊

    * 5.1您可以在關於頁面最底部檢視到「隱私政策」,點選即可檢視
    * 5.2停止運營向您告知
    如我們停止運營,我們將及時停止收集您個人資訊的活動,將停止運營的通知以逐一送達或公告的形式通知您,並對所持有的您的個人資訊進行刪除或匿名化處理。

    6. 未成年人條款

    * 6.1未成年人通用條款

    > a.若您是未滿18週歲的未成年人,在使用本應用及相關服務前,應在您的父母或其他監護人監護、指導下共同閱讀並同意本隱私政策。
    > b.我們根據國家相關法律法規的規定保護未成年人的個人資訊,只會在法律允許、父母或其他監護人明確同意或保護未成年人所必要的情況下收集、使用、共享或披露未成年人的個人資訊;如果我們發現在未事先獲得可證實的父母或其他監護人同意的情況下收集了未成年人的個人資訊,則會設法儘快刪除相關資訊。
    > c.若您是未成年人的監護人,當您對您所監護的未成年人的個人資訊有相關疑問時,請透過本隱私政策公示的聯絡方式與我們聯絡。

    7. 隱私政策的修訂和通知

    * 7.1.為了給您提供更好的服務,本應用及相關服務將不時更新與變化,我們會適時對本隱私政策進行修訂,這些修訂構成本隱私政策的一部分並具有等同於本隱私政策的效力,未經您明確同意,我們不會削減您依據當前生效的本隱私政策所應享受的權利。
    * 7.2.本隱私政策更新後,會以適當的方式提醒您更新的內容,以便您及時瞭解本隱私政策的最新版本。
    8. 關於第三方SDK名稱及獲取的相關資訊和使用的目的範圍

    * 8.1.友盟SDK
    合作目的:提供統計分析、社會化分享服務
    個人資訊收集方式:嵌入第三方SDK,SDK收集傳輸個人資訊
    個人資訊收集範圍:裝置Mac地址、唯一裝置識別碼(IMEI/Mac/android ID/IDFA/OPENUDID/GUID/BSSID/SSID/SN、SIM 卡 IMSI 資訊)
    隱私政策:https://www.umeng.com/page/policy

    * 8.2.Bugly SDK
    合作目的:為應用App提供了穩定性監控,追蹤奔潰閃退的問題
    個人資訊收集方式:嵌入第三方SDK,SDK收集傳輸個人資訊
    個人資訊收集範圍:裝置MAC地址、唯一裝置標識碼,App奔潰閃退日誌
    隱私政策:https://bugly.qq.com/v2/contract

    * 8.3.支付寶SDK
    合作目的:提供支付寶手機App第三方支付服務
    個人資訊收集方式:嵌入第三方SDK,SDK收集傳輸個人資訊
    個人資訊收集範圍:常用裝置資訊(如IMEI/IMSI、SIM卡序列號/MAC地址)、網路資訊以及地理位置資訊
    隱私政策:https://render.alipay.com/p/c/k2cx0tg8

    * 8.4.微信開放平臺SDK
    合作目的:支援微信授權登入、微信分享和微信支付
    個人資訊收集方式:嵌入第三方SDK,SDK收集傳輸個人資訊
    個人資訊收集範圍:裝置型號、作業系統、唯一裝置識別符號(指由裝置製造商編入到裝置中的一串字元,可用於以獨有方式標識相應裝置)
    隱私政策:https://weixin.qq.com/cgi-bin/readtemplate?lang=zh_CN&t=weixin_agreement&s=privacy

    * 8.5.七牛雲SDK
    合作目的:為應用提供圖片上傳功能
    個人資訊收集方式:嵌入第三方SDK,SDK收集傳輸個人資訊
    個人資訊收集範圍:裝置型號、作業系統、唯一裝置識別符號(指由裝置製造商編入到裝置中的一串字元,可用於以獨有方式標識相應裝置)
    隱私政策:https://developer.qiniu.com/pili/8027/sdk-privacy-policy

    9. 賬號登出說明
    您可以按照操作流程【"個人中心"->"頭像"->"登出賬號"】進行賬號登出。在您點選登出賬號時,我們會再次確認登出(避免誤操作)。在您登出成功後,我們將停止為您提供產品或服務,並根據適用法律的要求刪除您的個人資訊。

    10. 聯絡我們
    如果您對個人資訊保護問題有投訴、建議、疑問,您可以透過以下方式與我們取得聯絡:
    
    > 負責開發者:qsbye,
    > 郵箱地址:2557877116@qq.com
""".trimIndent()
/* end 隱私政策 markdown */

EulaActivity.kt

package cn.qsbye.alittlesmile_android

import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Star
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import cn.qsbye.alittlesmile_android.AboutPageClass.AboutScreen
import cn.qsbye.alittlesmile_android.AboutPageClass.BaseTextItem
import cn.qsbye.alittlesmile_android.AboutPageClass.LinkText
import cn.qsbye.alittlesmile_android.AboutPageClass.ModuleItem
import cn.qsbye.alittlesmile_android.AboutPageClass.MyViewModel
import cn.qsbye.alittlesmile_android.AboutPageClass.NormalSubItemWithStartIconData
import cn.qsbye.alittlesmile_android.AboutPageClass.NormalWithStartIconSubItemModule
import cn.qsbye.alittlesmile_android.AboutPageClass.RightsTextItem
import cn.qsbye.alittlesmile_android.AboutPageClass.boxSampleIcon
import cn.qsbye.alittlesmile_android.ui.theme.Alittlesmile_androidTheme
import io.noties.markwon.Markwon

class EulaActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Alittlesmile_androidTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // 顯示使用者協議頁內容
                    // MyEulaPage(MyViewModel(),this@EulaActivity) // 這裡假設你有一個 MyViewModel 例項
                    MyEulaPage(this@EulaActivity)

                }
            }
        }
    }
}

/* start EULA頁面佈局 */
@Composable
fun MyEulaPage(context_input:Activity, modifier: Modifier = Modifier) {
    /* start markdown顯示 */
    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp),
        factory = { ctx ->
            val layout_from_xml =
                LayoutInflater.from(ctx).inflate(R.layout.about_activity, null)
            val textview_from_xml = layout_from_xml.findViewById<TextView>(R.id.about_activity_textview) as TextView

            val markwon = Markwon.create(context_input)
            // render raw input to styled markdown
            // Set the styled markdown to the TextView
            markwon.setMarkdown(textview_from_xml, eula_markdown_text)
            layout_from_xml // Return the TextView instance
        },
    )
    /* end markdown顯示 */

}
/* end EULA頁面佈局 */

/* start 使用者許可 markdown */
val eula_markdown_text = """
    ## 使用者協議
    感謝你對「曉曉愛學」(以下簡稱"本應用")的支援。本應用作為一款學習輔助軟體,旨在提升你的學習體驗。
    
    ### 關於賬號系統
    本應用從1.0.0開始提供了賬號系統,目前僅僅用於記錄使用者身份(啟用狀態),該賬號使用郵箱註冊,註冊賬號時,請務必自行保證郵箱的有效性,後續將用於您忘記密碼時接收重置密碼連結,本應用不會將郵箱用於賬號以外的其他功能。
    為了公平和更好的運營下去,賬號只能自用,不允許分享和轉賣,若發現有人公開分享和轉賣,將取消或禁封賬號,避免給雙方帶來不必要的麻煩,請大家遵守契約。
    
    ### 使用者禁止行為
    你在使用本應用和/或本服務的過程中,應遵守相關法律法規、使用者協議、規則規範等,不得從事包括但不限於以下任何行為,也不得為以下任何行為提供便利
    1. 釋出、傳送、傳播、儲存危害國家安全統一、破壞社會穩定、違反公序良俗、侮辱、誹謗、淫穢、暴力以及任何違反國家法律法規的內容;
    2. 釋出、傳送、傳播、儲存侵害他人智慧財產權、商業秘密等合法權利的內容;
    3. 虛構事實、隱瞞真相以誤導、欺騙他人;
    4. 釋出、傳送、傳播廣告資訊及垃圾資訊;
    5. 其他法律法規禁止的行為。
    
    ### 協議的生效與變更
    你使用本應用的服務即視為你已閱讀本協議並接受本協議的約束。
    本應用有權在必要時修改本協議條款。你可以在相關服務頁面查閱最新版本的協議條款。
    本協議條款變更後,如果你繼續使用本應用提供的軟體或服務,即視為你已接受修改後的協議。如果你不接受修改後的協議,應當停止使用本應用提供的軟體或服務。
""".trimIndent()
/* end 使用者許可資訊 markdown */

「關於」頁模板部分
AboutPageClass/SampleDemo.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
import androidx.compose.material.icons.outlined.Star
import androidx.compose.material3.*
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cn.qsbye.alittlesmile_android.R

@Composable
fun HomeScreen(viewModel: MyViewModel) {
    Column(
        Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { viewModel.pagerIndex = 4 }) {
            Text(text = "仿 一個木函")
        }
    }
}

@Composable
fun AboutQqPager(myViewModel: MyViewModel) {
    val list = remember {
        listOf(
            NormalSubItemData("功能介紹", endIcon = endArrow),
            NormalSubItemData("官網", endIcon = endArrow, endText = "有新版本可用"),
            NormalSubItemData("幫助", endIcon = endArrow),
            NormalSubItemData("反饋", endIcon = endArrow)
        )
    }

    Column(
        Modifier
            .fillMaxSize()
            .background(Color(0xFFF6F7FB))) {
        AboutScreen(
            topContent = {
                AppInfoVerticalItem(
                    iconPaint = painterResource(id = R.drawable.ic_launcher_foreground),
                    subText = "V 8.9.3.8730",
                    modifier = Modifier.padding(bottom = 32.dp, top=16.dp),
                    subTextStyle = MaterialTheme.typography.bodyMedium.copy(color = Color(0xFFB3B3BD))
                )
            },
            mainContent = {
                NormalSubItemModule(
                    itemList = list,
                    backGroundColor = Color.White,
                    modifier = Modifier
                        .padding(horizontal = 12.dp)
                        .padding(bottom = 6.dp),
                    startTextStyle = LocalTextStyle.current.copy(fontSize = 16.sp),
                    endTextStyle = LocalTextStyle.current.copy(color = Color(0xFF939393)),
                    border = BorderStroke(width = 0.dp, color = Color.Transparent)
                )
            },
            bottomContent = {
                BaseTextItem {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        LinkText(text = "服務協議", color = Color(0xFF115AA9)) {}

                        Text(text=" | ", color = Color(0xFF5A9CD9))

                        LinkText(text = "隱私政策", color = Color(0xFF115AA9)) {}
                    }
                    Text(text = "客戶服務熱線-4006700700", color = Color(0xFFAFAFAF))
                    RightsTextItem(
                        dateText = "2009-2022",
                        name = "Tencent.",
                        style = LocalTextStyle.current.copy(color = Color(0xFFAFAFAF))
                    )
                }
            },
            keepBottomInBottom = true
        )
    }
}

@Composable
fun AboutBoxPager(myViewModel: MyViewModel) {
    val list = remember {
        listOf(
            NormalSubItemWithStartIconData("給我好評", subText = "您的好評將會給予我們莫大的動力與幫助", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("加入群組", subText = "加入官方群組,與四海函友交友互水", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("分享應用", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("小程式", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("反饋&建議", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("獲取幫助", startIcon = boxSampleIcon),
            NormalSubItemWithStartIconData("版本狀態", subText = "7.10.2-normal", startIcon = boxSampleIcon)
        )
    }

    Column(
        Modifier
            .fillMaxSize()
            .background(Color.White)) {
        AboutScreen(
            topContent = {
                Column(
                    modifier = Modifier.background(
                        brush = Brush.verticalGradient(
                            listOf(
                                Color(0xFF89B6A2),
                                Color.White
                            )
                        )
                    )
                ) {
                    ModuleItem(
                        modifier = Modifier
                            .padding(12.dp)
                            .background(Color.White)) {
                        Column(modifier = Modifier.padding(bottom = 16.dp)) {
                            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                                Icon(painter = painterResource(id = R.drawable.ic_launcher_foreground), contentDescription = "logo")
                            }
                            BaseTextItem {
                                Text(
                                    text = "擁有很多,不如有我。",
                                    Modifier.padding(vertical = 6.dp),
                                    style = MaterialTheme.typography.titleLarge
                                )
                            }
                            Row(
                                Modifier
                                    .fillMaxWidth()
                                    .padding(top = 8.dp), horizontalArrangement = Arrangement.SpaceBetween) {
                                Row {
                                    Icon(
                                        imageVector = Icons.Outlined.Star,
                                        contentDescription = "star",
                                        modifier = Modifier.padding(start = 16.dp, end=12.dp),
                                        tint = Color.Cyan
                                    )

                                    Icon(
                                        imageVector = Icons.Outlined.Star,
                                        contentDescription = "star",
                                        tint = Color.Magenta
                                    )
                                }
                                LinkText(
                                    text = "開源許可",
                                    modifier = Modifier.padding(end = 16.dp),
                                    color = Color(0xFF89B6A2)
                                ) {}
                            }
                        }
                    }
                }
            },
            mainContent = {
                NormalWithStartIconSubItemModule(
                    itemList = list,
                    modifier = Modifier.padding(12.dp),
                    backGroundColor = Color.White,
                    showDivider = false,
                    elevation = 2.dp,
                    itemPadding = 24.dp,
                    textStyle = MaterialTheme.typography.titleLarge.copy(color = Color(0xFF404040)),
                    subTextStyle = MaterialTheme.typography.bodyMedium.copy(color = Color(0xFF797979)),
                    cardShape = RoundedCornerShape(12.dp), // 定義模組框的形狀,這裡使用圓角,且圓角大小為 12 dp
                    extraContent = {
                        Row(
                            Modifier
                                .fillMaxWidth()
                                .padding(top = 8.dp), horizontalArrangement = Arrangement.End) {
                            Row(Modifier.fillMaxWidth(0.5f), horizontalArrangement = Arrangement.SpaceEvenly) {
                                LinkText(text = "隱私政策", color = Color(0xFF89B6A2)) { }
                                LinkText(text = "使用者協議", color = Color(0xFF89B6A2)) { }
                            }
                        }
                    }
                )
            },
            bottomContent = {
                BaseTextItem {
                    Text(
                        text = "XX科技 版權所有",
                        modifier = Modifier.padding(bottom = 16.dp, start = 8.dp),
                        color = Color(0xFF797979)
                    )
                    RightsTextItem(
                        dateText = "2017-2022",
                        name = "XXkuraft",
                        style = LocalTextStyle.current.copy(color = Color(0xFF797979))
                    )
                }
            }
        )
    }
}

val endArrow =  @Composable {
    Icon(imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowRight, contentDescription = "arrow", tint = Color(0xFFD0D0D0))
}

val boxSampleIcon = @Composable {
    Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
        Icon(imageVector = Icons.Outlined.Star, contentDescription = "star", tint = Color(0xFF797979))
    }
}

@Preview
@Composable
fun PreviewDemo() {
    Column(modifier = Modifier.fillMaxSize()) {
        AboutLibPager()
    }
}

AboutScreen.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview

/**
 * AboutPager 的主入口,所有的子模組都應該放置於其中
 *
 * @param modifier Modifier
 * @param topContent 頂部內容(一般用於放置APP ico 、名稱、版本號等)
 * @param mainContent 中間內容(用於存放主要內容)
 * @param bottomContent 底部內容(一般用於存放版權資訊)
 * @param keepBottomInBottom 是否固定底部內容始終在底部可見,設定為 true 可能出現內容疊加
 * */
@Composable
fun AboutScreen(
    modifier: Modifier = Modifier,
    topContent: (@Composable () -> Unit)? = null,
    mainContent: (@Composable () -> Unit)? = null,
    bottomContent: (@Composable () -> Unit)? = null,
    keepBottomInBottom: Boolean = false
) {
    Box {
        Column(modifier = modifier.verticalScroll(rememberScrollState())) {
            if (topContent != null) {
                topContent()
            }
            if (mainContent != null) {
                mainContent()
            }
            if (bottomContent != null && !keepBottomInBottom) {
                bottomContent()
            }
        }

        if (bottomContent != null && keepBottomInBottom) {
            Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom) {
                bottomContent()
            }
        }
    }
}

AppinfoItem.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cn.qsbye.alittlesmile_android.R

/**
 * 頭部 APP 資訊垂直顯示基礎 Item
 *
 * @param iconContent 圖示內容
 * @param textContent 文字內容
 * @param modifier Modifier
 * */
@Composable
fun AppInfoVerticalItem(
    iconContent: @Composable () -> Unit,
    textContent: @Composable () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(modifier = modifier) {
        iconContent()
        textContent()
    }
}

/**
 * 頭部 APP 資訊垂直顯示 item
 *
 * @param iconPaint APP 圖示
 * @param modifier Modifier
 * @param text 主文字
 * @param subText 主文字下方的補充文字
 * @param textStyle 主文字樣式
 * @param subTextStyle 補充文字樣式
 * */
@Composable
fun AppInfoVerticalItem(
    iconPaint: Painter,
    modifier: Modifier = Modifier,
    text: String? = null,
    subText: String? = null,
    textStyle: TextStyle = MaterialTheme.typography.titleMedium,
    subTextStyle: TextStyle = MaterialTheme.typography.bodyMedium
) {
    AppInfoVerticalItem(
        iconContent = {
            Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                Icon(
                    painter = iconPaint,
                    contentDescription = "icon"
                )
            }
        },
        textContent = {
            Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {

                if (text != null) {
                    Text(
                        text = text,
                        style = textStyle,
                        maxLines = 1
                    )
                }

                if (subText != null) {
                    Text(
                        text = subText,
                        style = subTextStyle
                    )
                }

            }
        },
        modifier = modifier
    )
}

/**
 * 頭部 APP 資訊水平顯示基礎 Item
 *
 * @param iconContent 圖示內容
 * @param textContent 文字內容
 * @param modifier Modifier
 * */
@Composable
fun AppInfoHorizontalItem(
    iconContent: @Composable () -> Unit,
    textContent: @Composable () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceBetween) {
        iconContent()
        textContent()
    }
}

/**
 * 頭部 APP 資訊水平顯示 item
 *
 * @param iconPaint APP 圖示
 * @param modifier Modifier
 * @param text 主文字
 * @param subText 主文字下方的補充文字
 * */
@Composable
fun AppInfoHorizontalItem(
    iconPaint: Painter,
    text: String,
    modifier: Modifier = Modifier,
    subText: String? = null
) {
    AppInfoHorizontalItem(
        iconContent = {
            Icon(
                painter = iconPaint,
                contentDescription = "icon"
            )
        },
        textContent = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier.fillMaxHeight().padding(6.dp)
            ) {
                Text(
                    text = text,
                    style = MaterialTheme.typography.titleMedium,
                    maxLines = 1
                )

                if (subText != null) {
                    Text(
                        text = subText,
                        style = MaterialTheme.typography.bodyMedium
                    )
                }

            }
        },
        modifier = modifier.fillMaxWidth()
    )
}


@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
fun PreviewVerticalAppInfoItem() {
    AppInfoVerticalItem(
        iconPaint = painterResource(id = R.drawable.ic_launcher_foreground),
        text = "示例APP",
        subText = "V1.0.0"
    )
}

@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
fun PreviewHorizontalAppInfoItem() {
    AppInfoHorizontalItem(
        iconPaint = painterResource(id = R.drawable.ic_launcher_foreground),
        text = "示例APP",
        subText = "V1.0.0",
        modifier = Modifier.height(80.dp)
    )
}

ModuleItem.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.KeyboardArrowRight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import cn.qsbye.alittlesmile_android.R

/**
 * 一個獨立的模組,使用 Card 作為載體
 *
 * @param modifier Modifier
 * @param title 模組標題,會被置於載體 Card 之外
 * @param backGroundColor Card 背景顏色
 * @param border Card 變寬
 * @param elevation Card 高度(會影響 Card 的陰影深度)
 * @param shape Card 形狀(可自定義形狀或增加圓角)
 * @param titleStyle 標題文字的樣式
 * @param content 內容
 * */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModuleItem(
    modifier: Modifier = Modifier,
    title: String? = null,
    backGroundColor: Color = Color.Unspecified,
    border: BorderStroke? = null,
    elevation: Dp = 1.dp,
    shape: Shape = MaterialTheme.shapes.medium,
    titleStyle: TextStyle = MaterialTheme.typography.titleMedium,
    content: @Composable () -> Unit
) {
    Column {
        if (title != null) {
            // 將標題文字放到 Card 外面,這樣比較美觀
            Text(
                text = title,
                style = titleStyle,
                modifier = Modifier.padding(start = 6.dp)
            )
        }

        Card(
            // onClick = { }, // 其實這裡只是將 card 作為外部載體使用,不需要直接點選 card,但是這個引數是必須的,所以直接傳一個空的給它
            // enabled = false, // 禁用點選
            modifier = modifier.fillMaxWidth(),
            colors = CardDefaults.cardColors(containerColor = backGroundColor),
            border = border,
            elevation = CardDefaults.cardElevation(elevation), // 這裡需要將 Dp 型別的值轉換為 CardElevation
            shape = shape
        ) {
            Column(modifier = Modifier.padding(8.dp)) {
                content()
            }
        }
    }
}

/**
 * 基礎 Item 載體,使用 Row 作為載體承載內容
 *
 * @param startContent 左邊的內容
 * @param endContent 右邊的內容
 * @param modifier Modifier
 * @param horizontalArrangement 水平對齊方式
 * @param verticalAlignment 垂直對齊方式
 * */
@Composable
fun BaseSubItem(
    startContent: @Composable () -> Unit,
    endContent: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
    verticalAlignment: Alignment.Vertical = Alignment.Top
) {
    Row(
        modifier = modifier.fillMaxWidth(),
        horizontalArrangement = horizontalArrangement,
        verticalAlignment = verticalAlignment
    ) {
        startContent()
        endContent()
    }
}

/**
 * 一般 Item,顯示效果形如普通文字列表,但是可以新增尾部圖示與尾部文字,建議包裹在 [NormalSubItemModule] 使用
 *
 * @param startText 主文字,位於列表最左邊
 * @param modifier Modifier
 * @param endText 輔助文字,位於列表最右邊,但是在 [endIcon] 左邊
 * @param startTextStyle 主文字樣式
 * @param endTextStyle 輔助文字樣式
 * @param endIcon 尾部圖示
 * @param onClick 點選回撥
 * */
@Composable
fun NormalSubItem(
    startText: String,
    modifier: Modifier = Modifier,
    endText: String? = null,
    startTextStyle: TextStyle = LocalTextStyle.current,
    endTextStyle: TextStyle = LocalTextStyle.current,
    endIcon: (@Composable () -> Unit)? = null,
    onClick: (() -> Unit)? = null
) {
    BaseSubItem(
        // 這裡如果沒有設定點選回撥則不新增 clickable,因為如果即使新增的是空 lambda 也會有點選漣漪效果,不太美觀
        modifier = if (onClick != null) modifier.clickable { onClick() } else modifier,
        startContent = {
            Text(text = startText, style = startTextStyle)
        },
        endContent = {
            Row(verticalAlignment = Alignment.CenterVertically) {
                if (endText != null) {
                    Text(text = endText, style = endTextStyle)
                }
                if (endIcon != null) {
                    endIcon()
                }
            }
        },
        verticalAlignment = Alignment.CenterVertically
    )
}

/**
 * 帶有前導圖示的一般 Item ,顯示效果類似對話方塊,建議包裹在 [NormalWithStartIconSubItemModule] 中使用
 *
 * @param text 主文字
 * @param modifier Modifier
 * @param subText 輔助文字
 * @param textStyle 主文字樣式
 * @param subTextStyle 輔助文字樣式
 * @param isAlignIcon 是否留空,如果設定為 true,那麼即使沒有圖示也會將圖示位置留空佔位
 * @param startIcon 圖示
 * @param onClick 點選回撥
 * */
@Composable
fun NormalWithStartIconSubItem(
    text: String,
    modifier: Modifier = Modifier,
    subText: String? = null,
    textStyle: TextStyle = MaterialTheme.typography.titleMedium,
    subTextStyle: TextStyle = MaterialTheme.typography.bodySmall,
    isAlignIcon: Boolean = true,
    startIcon: (@Composable () -> Unit)? = null,
    onClick: (() -> Unit)? = null
) {
    BaseSubItem(
        modifier = if (onClick != null) modifier.clickable { onClick() } else modifier,
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
        startContent = {
            // 如果條件滿足的話,即使圖示為空,也要把位置留空出來,即 fillMaxWidth(0.2f)
            Column(Modifier.fillMaxWidth(if (!isAlignIcon && startIcon == null) 0f else 0.2f)) {
                if (startIcon != null) {
                    startIcon()
                }
            }

            Column(modifier = Modifier.padding(start = 6.dp).fillMaxWidth(0.8f)) {
                Text(text = text, style = textStyle)
                if (subText != null) {
                    Text(text = subText, style = subTextStyle)
                }
            }
        },
        // 由於該元件風格不是嚴格的左右佈局,所以這裡將內容全放入 startContent 中,不使用 endContent
        endContent = {  }
    )
}

/**
 *
 * 用於承載 [NormalSubItem] 的模組,會將傳入的所有 [NormalSubItem] 放入同一個 Card 中
 *
 * @param itemList 內容列表
 * @param modifier 承載這個 module 的父 composable 的 modifier
 * @param title 該模組標題
 * @param showDivider 是否在 item 之間顯示分割線
 * @param showAllDivider 是否在第一個 item 之前 和 最後一個 item 之後顯示分割線
 * @param backGroundColor 模組背景顏色
 * @param border 模組邊框
 * @param elevation 模組高度(影響陰影深度)
 * @param itemPadding item 之間的 padding
 * @param startTextStyle 主文字樣式
 * @param endTextStyle 輔助文字樣式
 * @param cardShape 模組形狀
 * @param titleStyle 標題樣式
 * @param extraContent 額外的子定義內容,會在最後顯示
 * @param onClick 點選回撥,引數為點選的 item 索引
 * */
@Composable
fun NormalSubItemModule(
    itemList: List<NormalSubItemData>,
    modifier: Modifier = Modifier,
    title: String? = null,
    showDivider: Boolean = true,
    showAllDivider: Boolean = false,
    backGroundColor: Color = Color.Unspecified,
    border: BorderStroke? = null,
    elevation: Dp = 1.dp,
    itemPadding: Dp = 6.dp,
    startTextStyle: TextStyle = LocalTextStyle.current,
    endTextStyle: TextStyle = LocalTextStyle.current,
    cardShape: Shape = MaterialTheme.shapes.medium,
    titleStyle: TextStyle = MaterialTheme.typography.titleMedium,
    extraContent: (@Composable () -> Unit)? = null,
    onClick: ((clickNo: Int) -> Unit)? = null
) {
    ModuleItem(
        title = title,
        modifier = modifier,
        backGroundColor = backGroundColor,
        border = border,
        elevation = elevation,
        shape = cardShape,
        titleStyle = titleStyle
    ) {
        if (showAllDivider) {
            Divider(modifier = Modifier.padding(bottom = itemPadding))
        }
        itemList.forEachIndexed { index: Int, normalSubItemData: NormalSubItemData ->
            NormalSubItem(
                normalSubItemData.startText,
                endIcon = normalSubItemData.endIcon,
                onClick =
                    if (onClick == null) null
                    else { { onClick(index) } },
                endText = normalSubItemData.endText,
                modifier = Modifier.padding(bottom = if (!showAllDivider && index == itemList.lastIndex) 0.dp else itemPadding),
                startTextStyle = startTextStyle,
                endTextStyle = endTextStyle
            )
            if (showDivider && index != itemList.lastIndex) {
                Divider(modifier = Modifier.padding(bottom = itemPadding))
            }
        }
        if (showAllDivider) {
            Divider(modifier = Modifier.padding(bottom = itemPadding))
        }
        if (extraContent != null) {
            extraContent()
        }
    }
}

/**
 *
 * 用於承載 [NormalWithStartIconSubItem] 的模組,會將傳入的所有 [NormalWithStartIconSubItem] 放入同一個 Card 中
 *
 * @param itemList 內容列表
 * @param modifier 承載這個 module 的父 composable 的 modifier
 * @param title 該模組標題
 * @param showDivider 是否在 item 之間顯示分割線
 * @param showAllDivider 是否在第一個 item 之前 和 最後一個 item 之後顯示分割線
 * @param backGroundColor 模組背景顏色
 * @param border 模組邊框
 * @param elevation 模組高度(影響陰影深度)
 * @param itemPadding item 之間的 padding
 * @param textStyle 主文字樣式
 * @param subTextStyle 輔助文字樣式
 * @param cardShape 模組形狀
 * @param titleStyle 標題樣式
 * @param extraContent 額外的子定義內容,會在最後顯示
 * @param onClick 點選回撥,引數為點選的 item 索引
 * */
@Composable
fun NormalWithStartIconSubItemModule(
    itemList: List<NormalSubItemWithStartIconData>,
    modifier: Modifier = Modifier,
    title: String? = null,
    showDivider: Boolean = true,
    showAllDivider: Boolean = false,
    backGroundColor: Color = Color.Unspecified,
    border: BorderStroke? = null,
    elevation: Dp = 1.dp,
    itemPadding: Dp = 6.dp,
    isAlignIcon: Boolean = true,
    textStyle: TextStyle = MaterialTheme.typography.titleMedium,
    subTextStyle: TextStyle = MaterialTheme.typography.bodyMedium,
    cardShape: Shape = MaterialTheme.shapes.medium,
    titleStyle: TextStyle = MaterialTheme.typography.titleMedium,
    extraContent: (@Composable () -> Unit)? = null,
    onClick: ((clickNo: Int) -> Unit)? = null
) {
    ModuleItem(
        title = title,
        modifier = modifier,
        backGroundColor = backGroundColor,
        border = border,
        elevation = elevation,
        shape = cardShape,
        titleStyle = titleStyle
    ) {
        if (showAllDivider) {
            Divider(modifier = Modifier.padding(bottom = itemPadding))
        }
        itemList.forEachIndexed { index: Int, normalSubItemWithStartIconData: NormalSubItemWithStartIconData ->
            NormalWithStartIconSubItem(
                normalSubItemWithStartIconData.text,
                subText = normalSubItemWithStartIconData.subText,
                startIcon = normalSubItemWithStartIconData.startIcon,
                onClick =
                if (onClick == null) null
                else { { onClick(index) } },
                modifier = Modifier.padding(bottom = itemPadding),
                textStyle = textStyle,
                subTextStyle = subTextStyle,
                isAlignIcon = isAlignIcon
            )
            if (showDivider && index != itemList.lastIndex) {
                Divider(modifier = Modifier.padding(bottom = itemPadding))
            }
        }
        if (showAllDivider) {
            Divider(modifier = Modifier.padding(bottom = itemPadding))
        }
        if (extraContent != null) {
            extraContent()
        }
    }
}

data class NormalSubItemData(
    val startText: String,
    val endText: String? = null,
    val endIcon: (@Composable () -> Unit)? = null,
)

data class NormalSubItemWithStartIconData(
    val text: String,
    val subText: String? = null,
    val startIcon: (@Composable () -> Unit)? = null,
)


// ----------- 預覽 ------------


@Preview(showBackground = true, backgroundColor = 0xFF000000)
@Composable
fun PreviewModuleItem() {
    Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        ModuleItem(
            modifier = Modifier.padding(horizontal = 8.dp)
        ) {
            Text(text = "hahah")
        }
    }
}

@Preview(showBackground = true, backgroundColor = 0xFF000000)
@Composable
fun PreviewNormalSubItem() {
    val text = remember { mutableStateOf("測試列表項") }

    Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
        ModuleItem {
            NormalSubItem(
                text.value,
                endIcon = {
                    Icon(
                        imageVector = Icons.Outlined.KeyboardArrowRight,
                        contentDescription = "arrow"
                    )
                },
                onClick = {
                    text.value = "點選了:${System.currentTimeMillis()}"
                }
            )
        }
    }
}

@Preview(showBackground = true, backgroundColor = 0xFFCCCCCC)
@Composable
fun PreviewNormalWithStartIconSubItem() {
    val text = remember { mutableStateOf("測試列表項") }

    Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
        ModuleItem(title = "模組1") {
            NormalWithStartIconSubItem(
                text = text.value,
                startIcon = {
                    Icon(
                        painter = painterResource(id = R.drawable.ic_launcher_foreground),
                        contentDescription = "arrow"
                    )
                },
                subText = "這裡是一些其他的描述內容",
                onClick = {
                    text.value = "點選了:${System.currentTimeMillis()}"
                }
            )
        }
    }
}

@Preview(showBackground = true, backgroundColor = 0xFFCCCCCC)
@Composable
fun PreviewNormalSubItemModule() {
    val endIcon = @Composable {
        Icon(imageVector = Icons.Outlined.KeyboardArrowRight, contentDescription = "arrow")
    }
    val itemList1 = listOf(
        NormalSubItemData("列表1", "1.0.0", endIcon),
        NormalSubItemData("列表2", null, endIcon),
        NormalSubItemData("列表3", "1.0.0", endIcon),
        NormalSubItemData("列表4", "1.0.0", endIcon),
        NormalSubItemData("列表5", "1.0.0", endIcon),
        NormalSubItemData("列表6", "1.0.0"),
        NormalSubItemData("列表7", null, endIcon),
        NormalSubItemData("列表8", "1.0.0",),
        NormalSubItemData("列表9", "1.0.0", endIcon),
        NormalSubItemData("列表10", "1.0.0"),
    )

    Column(Modifier.fillMaxSize()) {
        NormalSubItemModule(
            itemList1, title = "模組1"
        )
        NormalSubItemModule(
            itemList1, title = "模組2"
        )
    }
}

@Preview(showBackground = true, backgroundColor = 0xFFCCCCCC)
@Composable
fun PreviewNormalWithStartIconSubItemModule() {
    val startIcon = @Composable {
        Icon(painter = painterResource(id = R.drawable.ic_launcher_foreground), contentDescription = "arrow")
    }
    val itemList1 = listOf(
        NormalSubItemWithStartIconData("列表1", "這是描述文字", startIcon),
        NormalSubItemWithStartIconData("列表2", null, startIcon),
        NormalSubItemWithStartIconData("列表3", "這是描述文字"),
        NormalSubItemWithStartIconData("列表4", "這是描述文字", startIcon),
        NormalSubItemWithStartIconData("列表5", "這是描述文字", startIcon),
        NormalSubItemWithStartIconData("列表6", "這是描述文字"),
        NormalSubItemWithStartIconData("列表7", null, startIcon),
        NormalSubItemWithStartIconData("列表8", "1.0.0",),
        NormalSubItemWithStartIconData("列表9", "1.0.0", startIcon),
        NormalSubItemWithStartIconData("列表10", "1.0.0"),
    )

    Column(Modifier.fillMaxSize()) {
        NormalWithStartIconSubItemModule(
            itemList1, title = "模組1", isAlignIcon = false
        )
        NormalWithStartIconSubItemModule(
            itemList1, title = "模組2"
        )
    }
}

MyViewModel.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.ViewModel
import cn.qsbye.alittlesmile_android.ui.theme.Alittlesmile_androidTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val viewModel = MyViewModel()

        setContent {
            HomeView(viewModel)
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeView(viewModel: MyViewModel) {
    Alittlesmile_androidTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            MaterialTheme {
                Scaffold(
                    topBar = {
                        TopAppBar (
                            title = {
                                Text(text = "About pager Demo", maxLines = 1, overflow = TextOverflow.Ellipsis)
                            },
                            navigationIcon = {
                                IconButton(onClick = {
                                    viewModel.pagerIndex = 0
                                }) {
                                    Icon(Icons.Outlined.ArrowBack, "返回")
                                }
                            },
                        )
                    }
                ) {
                    Column(Modifier.fillMaxSize().padding(it)) {
                        when (viewModel.pagerIndex) {
                            0 -> HomeScreen(viewModel)
                            1 -> AboutChengPager(MyViewModel())
                            2 -> AboutWechatPager(MyViewModel())
                            3 -> AboutQqPager((MyViewModel()))
                            4 -> AboutBoxPager(MyViewModel())
                            5 -> AboutLibPager()
                            6 -> AboutVspPager()
                        }
                    }
                }
            }
        }
    }
}

class MyViewModel: ViewModel() {
    var pagerIndex by  mutableStateOf(0)
}


@Preview
@Composable
fun PreviewHomeView() {
    HomeView(viewModel = MyViewModel())
}

TextItem.kt

package cn.qsbye.alittlesmile_android.AboutPageClass

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

/**
 * 基礎純文字 item
 * */
@Composable
fun BaseTextItem(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
        content()
    }
}

/**
 * 版權資訊文字
 * */
@Composable
fun RightsTextItem(
    dateText: String,
    name: String,
    style: TextStyle = LocalTextStyle.current,
) {
    Box(
        modifier = Modifier
            .fillMaxSize() // 使 Box 佔據可用空間
            .padding(16.dp) // 新增內邊距
    ) {
        Text(
            text = "Copyleft © $dateText $name",
            style = style,
            modifier = Modifier.align(Alignment.Center) // 確保文字在 Box 內居中
        )
//        Text(
//            text = "All Rights Reserved.",
//            style = style,
//            modifier = Modifier.align(Alignment.Center) // 確保文字在 Box 內居中
//        )
    }
}

/**
 * 可點選的連結文字
 * */
@Composable
fun LinkText(
    text: String,
    modifier: Modifier = Modifier,
    fontSize: TextUnit = 12.sp,
    color: Color = MaterialTheme.colorScheme.primary,
    onClick: () -> Unit) {

    Text(
        text = text,
        color = color,
        fontSize = fontSize,
        modifier = modifier.noRippleClickable(onClick = onClick) )
}

/**
 * 沒有點選漣漪效果的 Clickable
 * */
inline fun Modifier.noRippleClickable(crossinline onClick: ()->Unit): Modifier = composed {
    this.clickable(indication = null,
        interactionSource = remember { MutableInteractionSource() }) {
        onClick()
    }
}

@Preview(showBackground = true, backgroundColor = 0xFFCCCCCC)
@Composable
fun PreviewTextItem() {
    Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom) {
        BaseTextItem {
            Row {
                LinkText(text = "隱私協議") { }
                Text(text = "|", modifier = Modifier.padding(horizontal = 4.dp))
                LinkText(text = "隱私政策") { }
            }

            RightsTextItem(dateText = "2022", name = "equationl")
        }
    }
}

效果

關於頁 使用者協議頁

相關文章