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 c033dd178..e1477a46e 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/core/build.gradle.kts b/core/build.gradle.kts index 20216805f..1846f1785 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 8a059b9dd..30010f581 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,19 +17,18 @@ */ 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 android.widget.Toolbar 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 @@ -38,64 +37,71 @@ import javax.inject.Inject 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 - } + ): View? = createFragmentView(inflater, container) +} - private fun sendDiagnosticReport() { - requireActivity().start() - } - - override fun onDestroyView() { - super.onDestroyView() - fragmentHelpBinding?.root?.removeAllViews() - 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 000000000..2cba40c59 --- /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 000000000..e9a90cdb1 --- /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 000000000..6e6fb74af --- /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)