#4159 migrated Help Fragment to jetpack Compose

This commit is contained in:
Soumen 2025-02-07 22:47:10 +05:30 committed by MohitMaliFtechiz
parent 102162fde3
commit 6873bd53c2
8 changed files with 427 additions and 44 deletions

View File

@ -22,6 +22,9 @@ import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.help.HelpFragment
class KiwixHelpFragment : HelpFragment() {
override val navHostFragmentId: Int
get() = org.kiwix.kiwixmobile.R.id.nav_host_fragment
override fun rawTitleDescriptionMap() =
if (sharedPreferenceUtil.isPlayStoreBuildWithAndroid11OrAbove()) {
listOf(

View File

@ -366,4 +366,44 @@ object Libs {
*/
const val fetch: String = "com.github.tonyofrancis.Fetch:fetch2:" + Versions.fetch
const val fetchOkhttp: String = "com.github.tonyofrancis.Fetch:fetch2okhttp:" + Versions.fetch
/**
* https://developer.android.com/reference/kotlin/androidx/compose/material3
*/
const val androidx_compose_material3: String =
"androidx.compose.material3:material3-android:" + Versions.androidx_compose_material3_version
/**
* https://developer.android.com/reference/kotlin/androidx/activity/compose
*/
const val androidx_activity_compose: String =
"androidx.activity:activity-compose:" + Versions.androidx_activity_compose_version
/**
* https://developer.android.com/develop/ui/compose/documentation
*/
const val androidx_compose_ui: String =
"androidx.compose.ui:ui:" + Versions.androidx_compose_ui_version
const val androidx_compose_bom: String =
"androidx.compose:compose-bom:" + Versions.androidx_compose_bom_version
const val androidx_compose_tooling_preview: String =
"androidx.compose.ui:ui-tooling-preview"
const val androidx_compose_runtime_livedata: String =
"androidx.compose.runtime:runtime-livedata"
const val androidx_compose_runtime_rxjava2: String =
"androidx.compose.runtime:runtime-rxjava2"
/**
* testing libraries for compose
*/
const val androidx_compose_ui_test_junit4: String =
"androidx.compose.ui:ui-test-junit4"
const val androidx_compose_ui_tooling: String =
"androidx.compose.ui:ui-tooling"
}

View File

@ -115,6 +115,18 @@ object Versions {
const val keeper = "0.16.1"
const val fetch: String = "3.4.1"
const val org_jetbrains_kotlin_plugin_compose = "2.1.10"
const val kotlin_compiler_extension_version = "1.5.15"
const val androidx_compose_material3_version = "1.3.1"
const val androidx_activity_compose_version = "1.10.0"
const val androidx_compose_ui_version = "1.7.7"
const val androidx_compose_bom_version = "2025.01.01"
}
/**

View File

@ -13,6 +13,8 @@ buildscript {
}
plugins {
`android-library`
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose") version Versions.org_jetbrains_kotlin_plugin_compose
}
plugins.apply(KiwixConfigurationPlugin::class)
apply(plugin = "io.objectbox")
@ -26,6 +28,12 @@ android {
isMinifyEnabled = false
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.kotlin_compiler_extension_version
}
}
fun shouldUseLocalVersion() = File(projectDir, "libs").exists()
@ -63,4 +71,17 @@ dependencies {
implementation(Libs.kotlinx_coroutines_android)
implementation(Libs.kotlinx_coroutines_rx3)
implementation(Libs.zxing)
implementation(Libs.androidx_compose_material3)
implementation(Libs.androidx_activity_compose)
implementation(Libs.androidx_compose_ui)
implementation(platform(Libs.androidx_compose_bom))
implementation(Libs.androidx_compose_ui_tooling)
implementation(Libs.androidx_compose_runtime_livedata)
implementation(Libs.androidx_compose_runtime_rxjava2)
// For Compose UI Testing
androidTestImplementation(Libs.androidx_compose_ui_test_junit4)
debugImplementation(Libs.androidx_compose_ui_tooling)
}

View File

@ -17,83 +17,91 @@
*/
package org.kiwix.kiwixmobile.core.help
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.compose.ui.platform.ComposeView
import androidx.navigation.Navigation
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.databinding.FragmentHelpBinding
import org.kiwix.kiwixmobile.core.error.DiagnosticReportActivity
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@Suppress("UnnecessaryAbstractClass")
abstract class HelpFragment : BaseFragment() {
@Inject
lateinit var sharedPreferenceUtil: SharedPreferenceUtil
private var fragmentHelpBinding: FragmentHelpBinding? = null
protected open fun rawTitleDescriptionMap(): List<Pair<Int, Any>> = emptyList()
override val fragmentToolbar: Toolbar? by lazy {
fragmentHelpBinding?.root?.findViewById(R.id.toolbar)
}
override val fragmentTitle: String? by lazy { getString(R.string.menu_help) }
private val titleDescriptionMap by lazy {
rawTitleDescriptionMap().associate { (title, description) ->
val descriptionValue = when (description) {
is String -> description
is Int -> resources.getStringArray(description).joinToString(separator = "\n")
else -> {
throw IllegalArgumentException("Invalid description resource type for title: $title")
}
protected abstract val navHostFragmentId: Int
// Instead of keeping the XML binding, we now directly return a ComposeView.
protected open fun createFragmentView(
inflater: LayoutInflater,
container: ViewGroup?
): View {
return ComposeView(requireContext()).apply {
setContent {
// Create the helpScreen data using your rawTitleDescriptionMap.
val helpScreenData = transformToHelpScreenData(
requireContext(),
rawTitleDescriptionMap()
)
// Retrieve the NavController if your composable needs it.
val navController = Navigation.findNavController(requireActivity(), navHostFragmentId)
// Call your HelpScreen composable.
HelpScreen(data = helpScreenData, navController = navController)
}
getString(title) to descriptionValue
}
}
// Each subclass is responsible for providing its own raw data.
protected open fun rawTitleDescriptionMap(): List<Pair<Int, Any>> = emptyList()
// The following properties are now optional if no longer use an XML toolbar or title,
// we can remove or update these accordingly.
override val fragmentToolbar: Toolbar? by lazy {
// Already Applied ad TopAppBAr in scaffold in composable
null
}
override val fragmentTitle: String? by lazy { getString(R.string.menu_help) }
override fun inject(baseActivity: BaseActivity) {
(baseActivity as CoreMainActivity).cachedComponent.inject(this)
}
// Remove or adjust onViewCreated if you no longer need to manipulate XML-based views.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as AppCompatActivity
fragmentHelpBinding?.activityHelpDiagnosticImageView?.setOnClickListener {
sendDiagnosticReport()
}
fragmentHelpBinding?.activityHelpDiagnosticTextView?.setOnClickListener {
sendDiagnosticReport()
}
fragmentHelpBinding?.activityHelpRecyclerView?.addItemDecoration(
DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)
)
fragmentHelpBinding?.activityHelpRecyclerView?.adapter = HelpAdapter(titleDescriptionMap)
// Any additional logic that is independent of the XML layout can be kept here.
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentHelpBinding =
FragmentHelpBinding.inflate(inflater, container, false)
return fragmentHelpBinding?.root
}
): View? = createFragmentView(inflater, container)
}
private fun sendDiagnosticReport() {
requireActivity().start<DiagnosticReportActivity>()
}
override fun onDestroyView() {
super.onDestroyView()
fragmentHelpBinding = null
// Util function to modify the data accordingly
fun transformToHelpScreenData(
context: Context,
rawTitleDescriptionMap: List<Pair<Int, Any>>
): List<HelpScreenItemDataClass> {
return rawTitleDescriptionMap.map { (titleResId, description) ->
val title = context.getString(titleResId)
val descriptionValue = when (description) {
is String -> description
is Int -> context.resources.getStringArray(description).joinToString(separator = "\n")
else -> {
throw IllegalArgumentException("Invalid description resource type for title: $titleResId")
}
}
HelpScreenItemDataClass(title, descriptionValue)
}
}

View File

@ -0,0 +1,152 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.help
import android.app.Activity
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.error.DiagnosticReportActivity
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HelpScreen(
modifier: Modifier = Modifier,
data: List<HelpScreenItemDataClass>,
navController: NavController
) {
val context = LocalContext.current
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor =
if (isDarkTheme) colorResource(id = R.color.mine_shaft_gray900) else Color.White
val dividerColor =
if (isDarkTheme) colorResource(id = R.color.mine_shaft_gray600) else colorResource(
id = R.color.mine_shaft_gray350
)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
modifier = modifier.padding(start = 16.dp),
text = stringResource(id = R.string.menu_help),
color = Color.White // Set title text color to white
)
},
navigationIcon = {
IconButton(onClick = navController::popBackStack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.White // Set navigation icon color to white
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Black // Set top app bar background color to black
)
)
},
containerColor = backgroundColor
) {
Column(
modifier = Modifier
.padding(it)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
(context as? Activity)?.start<DiagnosticReportActivity>()
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Image(
painter = painterResource(R.drawable.ic_feedback_orange_24dp),
contentDescription = "Feedback",
modifier = Modifier
.padding(16.dp)
)
Text(
text = stringResource(R.string.send_report),
color = if (isDarkTheme) Color.LightGray else Color.DarkGray,
fontSize = 18.sp
)
}
LazyColumn(
modifier = Modifier
.fillMaxWidth()
) {
itemsIndexed(data, key = { _, item -> item.title }) { index, item ->
HorizontalDivider(
color = dividerColor
)
HelpScreenItem(data = item)
}
item {
HorizontalDivider(
color = dividerColor
)
}
}
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.help
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun HelpScreenItem(
modifier: Modifier = Modifier,
data: HelpScreenItemDataClass,
initiallyOpened: Boolean = false
) {
var isOpen by remember { mutableStateOf(initiallyOpened) }
val isDarkTheme = isSystemInDarkTheme()
val itemColor = if (isDarkTheme) Color.White else Color.Black
val arrowRotation by animateFloatAsState(
targetValue = if (isOpen) 180f else 0f,
animationSpec = tween(300),
label = "arrowRotation"
)
val interactionSource = remember(::MutableInteractionSource)
Column(
modifier = modifier
.fillMaxWidth()
.padding(top = 12.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(interactionSource = interactionSource, indication = null, onClick = {
isOpen = !isOpen
})
.padding(horizontal = 16.dp)
) {
Text(
text = data.title,
fontSize = 18.sp,
color = itemColor,
fontWeight = FontWeight.SemiBold
)
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = "Open or Close DropDown",
modifier = Modifier
.graphicsLayer {
rotationZ = arrowRotation
}
.size(46.dp),
tint = itemColor
)
}
Spacer(modifier = Modifier.height(12.dp))
AnimatedVisibility(visible = isOpen) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
) {
Text(
text = data.description,
fontSize = 16.sp,
textAlign = TextAlign.Left,
color = itemColor,
modifier = Modifier.padding(bottom = 8.dp)
)
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.help
//same as HelpItem data class in HelpAdapter.kt
data class HelpScreenItemDataClass(val title: String, val description: String)