From 6873bd53c2b7cfc22ebdb3bfec2670ca315e8932 Mon Sep 17 00:00:00 2001 From: Soumen Date: Fri, 7 Feb 2025 22:47:10 +0530 Subject: [PATCH 1/2] #4159 migrated Help Fragment to jetpack Compose --- .../kiwixmobile/help/KiwixHelpFragment.kt | 3 + buildSrc/src/main/kotlin/Libs.kt | 40 +++++ buildSrc/src/main/kotlin/Versions.kt | 12 ++ core/build.gradle.kts | 21 +++ .../kiwixmobile/core/help/HelpFragment.kt | 96 ++++++----- .../kiwix/kiwixmobile/core/help/HelpScreen.kt | 152 ++++++++++++++++++ .../kiwixmobile/core/help/HelpScreenItem.kt | 125 ++++++++++++++ .../core/help/HelpScreenItemDataClass.kt | 22 +++ 8 files changed, 427 insertions(+), 44 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/help/KiwixHelpFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/help/KiwixHelpFragment.kt index 62e467caea..7dc0fdcd2b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/help/KiwixHelpFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/help/KiwixHelpFragment.kt @@ -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( diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 8de3a54d4f..7e3ee406ae 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -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" + } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index df019ada76..ced265fe72 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -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" } /** diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 20216805f3..1846f17850 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -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) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpFragment.kt index 3765ea7285..c3781b73ff 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpFragment.kt @@ -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> = 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 - getString(title) to descriptionValue + // 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) + } } } + // Each subclass is responsible for providing its own raw data. + protected open fun rawTitleDescriptionMap(): List> = 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 - } - - private fun sendDiagnosticReport() { - requireActivity().start() - } + ): View? = createFragmentView(inflater, container) +} - override fun onDestroyView() { - super.onDestroyView() - fragmentHelpBinding = null +// Util function to modify the data accordingly +fun transformToHelpScreenData( + context: Context, + rawTitleDescriptionMap: List> +): List { + 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) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt new file mode 100644 index 0000000000..2cba40c595 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt @@ -0,0 +1,152 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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, + 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() + }, + 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 + ) + } + } + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt new file mode 100644 index 0000000000..e9a90cdb13 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt @@ -0,0 +1,125 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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) + ) + } + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt new file mode 100644 index 0000000000..6e6fb74af3 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt @@ -0,0 +1,22 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +package org.kiwix.kiwixmobile.core.help + +//same as HelpItem data class in HelpAdapter.kt +data class HelpScreenItemDataClass(val title: String, val description: String) From 9f22bb475ea4c4134fd3fc880c45bbb8c0b742d7 Mon Sep 17 00:00:00 2001 From: Soumen Pal Date: Fri, 7 Mar 2025 23:02:09 +0530 Subject: [PATCH 2/2] Fixed Linted issuse related to Magic numbers and long methods and did the requested changes --- .../kiwixmobile/core/help/HelpAdapter.kt | 75 ------- .../kiwix/kiwixmobile/core/help/HelpScreen.kt | 185 +++++++++++------- .../kiwixmobile/core/help/HelpScreenItem.kt | 133 ++++++++----- .../core/help/HelpScreenItemDataClass.kt | 2 +- .../custom/help/CustomHelpFragment.kt | 30 ++- lintConfig.xml | 1 + 6 files changed, 230 insertions(+), 196 deletions(-) delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpAdapter.kt diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpAdapter.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpAdapter.kt deleted file mode 100644 index dadc3e0344..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpAdapter.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * 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 . - * - */ -package org.kiwix.kiwixmobile.core.help - -import android.animation.ObjectAnimator -import android.text.method.LinkMovementMethod -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.kiwix.kiwixmobile.core.base.adapter.BaseViewHolder -import org.kiwix.kiwixmobile.core.databinding.ItemHelpBinding -import org.kiwix.kiwixmobile.core.utils.AnimationUtils.collapse -import org.kiwix.kiwixmobile.core.utils.AnimationUtils.expand - -internal class HelpAdapter(titleDescriptionMap: Map) : - RecyclerView.Adapter() { - private var helpItems = titleDescriptionMap.map { (key, value) -> HelpItem(key, value) } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): Item = Item(ItemHelpBinding.inflate(LayoutInflater.from(parent.context), parent, false)) - - override fun onBindViewHolder( - holder: Item, - position: Int - ) { - holder.bind(helpItems[position]) - } - - override fun getItemCount(): Int = helpItems.size - - internal inner class Item(private val itemHelpBinding: ItemHelpBinding) : - BaseViewHolder(itemHelpBinding.root) { - - @SuppressWarnings("MagicNumber") - fun toggleDescriptionVisibility() { - if (itemHelpBinding.itemHelpDescription.visibility == View.GONE) { - ObjectAnimator.ofFloat(itemHelpBinding.itemHelpToggleExpand, "rotation", 0f, 180f).start() - itemHelpBinding.itemHelpDescription.expand() - } else { - ObjectAnimator.ofFloat(itemHelpBinding.itemHelpToggleExpand, "rotation", 180f, 360f).start() - itemHelpBinding.itemHelpDescription.collapse() - } - } - - override fun bind(item: HelpItem) { - itemHelpBinding.itemHelpTitle.setOnClickListener { toggleDescriptionVisibility() } - itemHelpBinding.itemHelpToggleExpand.setOnClickListener { toggleDescriptionVisibility() } - itemHelpBinding.itemHelpDescription.apply { - text = item.description - movementMethod = LinkMovementMethod.getInstance() - } - itemHelpBinding.itemHelpTitle.text = item.title - } - } -} - -class HelpItem(val title: String, val description: String) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt index 2cba40c595..5efb97b49e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreen.kt @@ -25,14 +25,16 @@ 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height 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.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -47,10 +49,12 @@ 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.platform.LocalDensity import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import androidx.navigation.NavController @@ -58,95 +62,144 @@ import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.error.DiagnosticReportActivity import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start -@OptIn(ExperimentalMaterial3Api::class) +val SendDiagnosticReportFontSize = 18.sp + @Composable fun HelpScreen( modifier: Modifier = Modifier, data: List, 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 - ) + 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 - ) - ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + HelpTopAppBar(navController) + } }, containerColor = backgroundColor - ) { + ) { innerPadding -> + HelpContent(data, dividerColor, innerPadding) + } +} - Column( - modifier = Modifier - .padding(it) - ) { +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HelpTopAppBar(navController: NavController) { + // Retrieve the actionBarSize from the current theme + val context = LocalContext.current + val actionBarHeight = with(LocalDensity.current) { + // Obtain the height defined in the theme (usually 56dp on phones) + val styledAttributes = + context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize)) + styledAttributes.getDimension(0, 0f).toDp().also { styledAttributes.recycle() } + } + + TopAppBar( + modifier = Modifier.height(actionBarHeight), // set the height here + title = { Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - (context as? Activity)?.start() - }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically ) { - 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 + modifier = Modifier.padding( + start = dimensionResource(R.dimen.activity_horizontal_margin) + ), + text = stringResource(id = R.string.menu_help), + color = Color.White, + fontWeight = FontWeight.SemiBold ) } - - LazyColumn( - modifier = Modifier - .fillMaxWidth() - ) { - itemsIndexed(data, key = { _, item -> item.title }) { index, item -> - HorizontalDivider( - color = dividerColor - ) - HelpScreenItem(data = item) - } - item { - HorizontalDivider( - color = dividerColor + }, + navigationIcon = { + Row(modifier = Modifier.fillMaxHeight(), verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = navController::popBackStack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back_Navigation", + tint = Color.White ) } } + + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Black + ) + ) +} + +@Composable +fun HelpContent( + data: List, + dividerColor: Color, + innerPadding: androidx.compose.foundation.layout.PaddingValues +) { + Column( + modifier = Modifier + .padding(innerPadding) + ) { + SendReportRow() + HelpItemList(data, dividerColor) + } +} + +@Composable +fun SendReportRow() { + val context = LocalContext.current + val isDarkTheme = isSystemInDarkTheme() + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + (context as? Activity)?.start() + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + Image( + painter = painterResource(R.drawable.ic_feedback_orange_24dp), + contentDescription = stringResource(R.string.send_report), + modifier = Modifier + .padding(dimensionResource(R.dimen.activity_horizontal_margin)) + ) + + Text( + text = stringResource(R.string.send_report), + color = if (isDarkTheme) Color.LightGray else Color.DarkGray, + fontSize = SendDiagnosticReportFontSize + ) + } +} + +@Composable +fun HelpItemList(data: List, dividerColor: Color) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + ) { + itemsIndexed(data, key = { _, item -> item.title }) { _, item -> + HorizontalDivider( + color = dividerColor + ) + HelpScreenItem(data = item) + } + item { + HorizontalDivider( + color = dividerColor + ) } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt index e9a90cdb13..a15ab7847a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItem.kt @@ -46,10 +46,23 @@ 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.res.dimensionResource +import androidx.compose.ui.res.stringResource 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.dp import androidx.compose.ui.unit.sp +import org.kiwix.kiwixmobile.core.R + +// Define constants for spacing, font sizes, etc. + +private val HelpItemTitleFontSize = 22.sp +private val HelpItemDescriptionFontSize = 17.sp +private val IconSize = 36.dp +private const val HelpItemAnimationDuration = 300 +private const val HelpItemArrowRotationOpen = 180f +private const val HelpItemArrowRotationClosed = 0f @Composable fun HelpScreenItem( @@ -58,68 +71,82 @@ fun HelpScreenItem( 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 itemColor = if (isSystemInDarkTheme()) Color.White else Color.Black - val interactionSource = remember(::MutableInteractionSource) + val topPadding: Dp = dimensionResource(id = R.dimen.dimen_medium_padding) + val horizontalPadding: Dp = dimensionResource(id = R.dimen.activity_horizontal_margin) Column( modifier = modifier - .fillMaxWidth() - .padding(top = 12.dp), + .fillMaxWidth(), 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 - ) + HelpItemHeader(data.title, isOpen, itemColor, horizontalPadding) { isOpen = !isOpen } + AnimatedVisibility(visible = isOpen) { + Spacer(modifier = Modifier.height(topPadding)) + HelpItemDescription(data.description, itemColor, horizontalPadding) } + } +} + +@Composable +fun HelpItemHeader( + title: String, + isOpen: Boolean, + itemColor: Color, + horizontalPadding: Dp, + onToggle: () -> Unit +) { + val arrowRotation by animateFloatAsState( + targetValue = if (isOpen) HelpItemArrowRotationOpen else HelpItemArrowRotationClosed, + animationSpec = tween(HelpItemAnimationDuration), + label = "arrowRotation" + ) + val interactionSource = remember(::MutableInteractionSource) - Spacer(modifier = Modifier.height(12.dp)) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable(interactionSource = interactionSource, indication = null, onClick = onToggle) + .padding(horizontal = horizontalPadding, vertical = horizontalPadding) + ) { + Text( + text = title, + fontSize = HelpItemTitleFontSize, + color = itemColor, + fontWeight = FontWeight.Normal + ) + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = stringResource(R.string.expand), + modifier = Modifier + .graphicsLayer { + rotationZ = arrowRotation + } + .size(IconSize), + tint = itemColor + ) + } +} - 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) - ) - } - } +@Composable +fun HelpItemDescription(description: String, itemColor: Color, horizontalPadding: Dp) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .padding(start = horizontalPadding, end = horizontalPadding) + ) { + Text( + text = description, + fontSize = HelpItemDescriptionFontSize, + textAlign = TextAlign.Left, + color = itemColor, + modifier = Modifier.padding(bottom = horizontalPadding), + fontWeight = FontWeight.Normal + ) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt index 6e6fb74af3..083d4238e4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/help/HelpScreenItemDataClass.kt @@ -18,5 +18,5 @@ package org.kiwix.kiwixmobile.core.help -//same as HelpItem data class in HelpAdapter.kt +// same as HelpItem data class in earlier in XML data class HelpScreenItemDataClass(val title: String, val description: String) diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/help/CustomHelpFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/help/CustomHelpFragment.kt index 32c8988249..b24cad1a48 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/help/CustomHelpFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/help/CustomHelpFragment.kt @@ -20,4 +20,32 @@ package org.kiwix.kiwixmobile.custom.help import org.kiwix.kiwixmobile.core.help.HelpFragment -class CustomHelpFragment : HelpFragment() +class CustomHelpFragment : HelpFragment() { + override val navHostFragmentId: Int + get() = org.kiwix.kiwixmobile.custom.R.id.custom_nav_controller + + override fun rawTitleDescriptionMap() = + if (sharedPreferenceUtil.isPlayStoreBuildWithAndroid11OrAbove()) { + listOf( + org.kiwix.kiwixmobile.core.R.string.help_2 to + org.kiwix.kiwixmobile.core.R.array.description_help_2, + org.kiwix.kiwixmobile.core.R.string.help_5 to + org.kiwix.kiwixmobile.core.R.array.description_help_5, + org.kiwix.kiwixmobile.core.R.string.how_to_update_content to + org.kiwix.kiwixmobile.core.R.array.update_content_description, + org.kiwix.kiwixmobile.core.R.string.why_copy_move_files_to_app_directory to + getString( + org.kiwix.kiwixmobile.core.R.string.copy_move_files_to_app_directory_description + ) + ) + } else { + listOf( + org.kiwix.kiwixmobile.core.R.string.help_2 to + org.kiwix.kiwixmobile.core.R.array.description_help_2, + org.kiwix.kiwixmobile.core.R.string.help_5 to + org.kiwix.kiwixmobile.core.R.array.description_help_5, + org.kiwix.kiwixmobile.core.R.string.how_to_update_content to + org.kiwix.kiwixmobile.core.R.array.update_content_description + ) + } +} diff --git a/lintConfig.xml b/lintConfig.xml index e043d2bdc1..c61a450f93 100644 --- a/lintConfig.xml +++ b/lintConfig.xml @@ -1,5 +1,6 @@ +