diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e5606174..661b6310 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -24,4 +24,4 @@ jobs: run: chmod +x gradlew - name: Build & run all tests - run: ./gradlew build --stacktrace --info \ No newline at end of file + run: ./gradlew build \ No newline at end of file diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/help/HelpActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/help/HelpActivity.java index 40e9849c..63ecde06 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/help/HelpActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/help/HelpActivity.java @@ -4,23 +4,32 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.annotation.Nullable; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.core.view.ViewCompat; import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.recyclerview.widget.RecyclerView; import com.d4rk.androidtutorials.java.BuildConfig; import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.ads.AdUtils; import com.d4rk.androidtutorials.java.databinding.ActivityHelpBinding; import com.d4rk.androidtutorials.java.databinding.DialogVersionInfoBinding; +import com.d4rk.androidtutorials.java.databinding.ItemHelpFaqBinding; import com.d4rk.androidtutorials.java.ui.components.navigation.BaseActivity; import com.d4rk.androidtutorials.java.ui.screens.help.repository.HelpRepository; -import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; import com.d4rk.androidtutorials.java.utils.OpenSourceLicensesUtils; import com.google.android.material.snackbar.Snackbar; import com.google.android.play.core.review.ReviewInfo; @@ -28,25 +37,36 @@ import dagger.hilt.android.AndroidEntryPoint; import me.zhanghai.android.fastscroll.FastScrollerBuilder; +import java.util.Arrays; +import java.util.List; + @AndroidEntryPoint public class HelpActivity extends BaseActivity { private HelpViewModel helpViewModel; + private static final List FAQ_ITEMS = Arrays.asList( + new FaqItem(R.string.question_1, R.string.summary_preference_faq_1), + new FaqItem(R.string.question_2, R.string.summary_preference_faq_2), + new FaqItem(R.string.question_3, R.string.summary_preference_faq_3), + new FaqItem(R.string.question_4, R.string.summary_preference_faq_4), + new FaqItem(R.string.question_5, R.string.summary_preference_faq_5), + new FaqItem(R.string.question_6, R.string.summary_preference_faq_6), + new FaqItem(R.string.question_7, R.string.summary_preference_faq_7), + new FaqItem(R.string.question_8, R.string.summary_preference_faq_8), + new FaqItem(R.string.question_9, R.string.summary_preference_faq_9) + ); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityHelpBinding binding = ActivityHelpBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - EdgeToEdgeDelegate.apply(this, binding.getRoot()); AdUtils.loadBanner(binding.faqNativeAd); helpViewModel = new ViewModelProvider(this).get(HelpViewModel.class); - new FastScrollerBuilder(binding.scrollContainer) + new FastScrollerBuilder(binding.scrollView) .useMd2Style() .build(); - getSupportFragmentManager().beginTransaction() - .replace(R.id.frame_layout_faq, new FaqFragment()) - .commit(); + bindFaqItems(binding); getSupportFragmentManager().beginTransaction() .replace(R.id.frame_layout_feedback, new FeedbackFragment()) @@ -121,13 +141,6 @@ private void openLink(String url) { startActivity(browserIntent); } - public static class FaqFragment extends PreferenceFragmentCompat { - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.preferences_faq, rootKey); - } - } - public static class FeedbackFragment extends PreferenceFragmentCompat { @Override @@ -157,6 +170,29 @@ public void onFailure(Exception e) { } } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + RecyclerView listView = getListView(); + listView.setNestedScrollingEnabled(false); + listView.setOverScrollMode(View.OVER_SCROLL_NEVER); + listView.setClipToPadding(false); + + ViewGroup.LayoutParams layoutParams = listView.getLayoutParams(); + FrameLayout.LayoutParams frameLayoutParams; + if (layoutParams instanceof FrameLayout.LayoutParams) { + frameLayoutParams = (FrameLayout.LayoutParams) layoutParams; + } else { + frameLayoutParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + } + frameLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + frameLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + listView.setLayoutParams(frameLayoutParams); + } + private void launchGooglePlayReviews() { Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + requireActivity().getPackageName() + "&showAllReviews=true"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); @@ -171,4 +207,58 @@ private void launchGooglePlayReviews() { } } } + + private void bindFaqItems(ActivityHelpBinding binding) { + LinearLayoutCompat faqList = binding.faqList; + faqList.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(this); + + for (int i = 0; i < FAQ_ITEMS.size(); i++) { + FaqItem item = FAQ_ITEMS.get(i); + ItemHelpFaqBinding itemBinding = ItemHelpFaqBinding.inflate(inflater, faqList, false); + itemBinding.question.setText(item.questionResId); + itemBinding.answer.setText(item.answerResId); + itemBinding.answer.setVisibility(View.GONE); + itemBinding.toggleIcon.setRotation(0f); + CharSequence questionText = itemBinding.question.getText(); + itemBinding.getRoot().setContentDescription(questionText); + itemBinding.questionContainer.setContentDescription(questionText); + ViewCompat.setStateDescription(itemBinding.getRoot(), getString(R.string.faq_state_collapsed)); + ViewCompat.setStateDescription(itemBinding.questionContainer, getString(R.string.faq_state_collapsed)); + + View.OnClickListener toggleListener = v -> toggleFaqItem(itemBinding); + itemBinding.getRoot().setOnClickListener(toggleListener); + itemBinding.questionContainer.setOnClickListener(toggleListener); + itemBinding.toggleIcon.setOnClickListener(toggleListener); + itemBinding.divider.setVisibility(i == FAQ_ITEMS.size() - 1 ? View.GONE : View.VISIBLE); + faqList.addView(itemBinding.getRoot()); + } + } + + private void toggleFaqItem(ItemHelpFaqBinding binding) { + boolean expand = binding.answer.getVisibility() != View.VISIBLE; + binding.answer.setVisibility(expand ? View.VISIBLE : View.GONE); + float rotation = expand ? 180f : 0f; + binding.toggleIcon.animate().cancel(); + binding.toggleIcon.animate() + .rotation(rotation) + .setDuration(200L) + .start(); + int stateRes = expand ? R.string.faq_state_expanded : R.string.faq_state_collapsed; + CharSequence stateDescription = getString(stateRes); + ViewCompat.setStateDescription(binding.getRoot(), stateDescription); + ViewCompat.setStateDescription(binding.questionContainer, stateDescription); + } + + private static final class FaqItem { + @StringRes + private final int questionResId; + @StringRes + private final int answerResId; + + private FaqItem(@StringRes int questionResId, @StringRes int answerResId) { + this.questionResId = questionResId; + this.answerResId = answerResId; + } + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_expand_more_24.xml b/app/src/main/res/drawable/ic_expand_more_24.xml new file mode 100644 index 00000000..3ac98a5e --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_more_24.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_help.xml b/app/src/main/res/layout/activity_help.xml index 8b2b2464..7c0bb058 100644 --- a/app/src/main/res/layout/activity_help.xml +++ b/app/src/main/res/layout/activity_help.xml @@ -1,55 +1,66 @@ - - + android:layout_height="0dp" + android:clipToPadding="false" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - - + android:orientation="vertical" + android:paddingStart="24dp" + android:paddingTop="16dp" + android:paddingEnd="24dp" + android:paddingBottom="32dp"> + + - - + + - - + android:layout_height="wrap_content" + android:orientation="vertical" /> + - + - + + + + + - - \ No newline at end of file + diff --git a/app/src/main/res/layout/ad_help.xml b/app/src/main/res/layout/ad_help.xml index 8f83104d..940166e6 100644 --- a/app/src/main/res/layout/ad_help.xml +++ b/app/src/main/res/layout/ad_help.xml @@ -8,7 +8,7 @@ style="@style/Widget.Material3.CardView.Filled" android:layout_width="match_parent" android:layout_height="wrap_content" - app:shapeAppearance="@style/ShapeAppearanceOverlay.CardViewBottomRoundedFilled"> + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.CardViewBottomRoundedFilled"> + android:layout_height="match_parent" + android:fillViewport="true"> - + android:orientation="vertical" + android:paddingHorizontal="24dp" + android:paddingVertical="24dp"> - + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical"> + android:textStyle="bold" /> - - + android:text="@string/app_version" /> - - - - - - - + + + + + + + - - + + + + + + + - + android:layout_marginTop="8dp" + android:gravity="center" + android:orientation="horizontal"> + + + + + + @@ -189,26 +183,21 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="24dp" - android:src="@drawable/il_about" - app:layout_constraintTop_toBottomOf="@id/animation_about" /> + android:src="@drawable/il_about" /> + android:text="@string/made_in" /> - - + android:layout_gravity="center_horizontal" + android:text="@string/copyright" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_help_faq.xml b/app/src/main/res/layout/item_help_faq.xml new file mode 100644 index 00000000..87dad559 --- /dev/null +++ b/app/src/main/res/layout/item_help_faq.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33b9b18e..41e01b8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -338,6 +338,8 @@ You can disable Firebase analytics and crashlytics by going to the settings menu in the app and toggle the switch for these features. You can check for updates to Android Studio Tutorials: Java Edition by going to the settings menu in the app and selecting the \"Check for updates\" option. You can support the development of Android Studio Tutorials: Java Edition by leaving a positive review on the Google Play Store, sharing the app with friends and colleagues, and supporting the developers through the \"Share\" option in the settings menu. + Collapsed + Expanded Allows the app to retrieve and use the advertising identifier associated with the user\'s device, providing personalized ads, measuring ad effectiveness, and showing ads on Android 13 or later devices. Allows the app to establish an internet connection to send error reports or check for updates. Allows the app to display notifications on the devices with Android 13 or later. diff --git a/app/src/main/res/xml/preferences_faq.xml b/app/src/main/res/xml/preferences_faq.xml deleted file mode 100644 index 21c7df79..00000000 --- a/app/src/main/res/xml/preferences_faq.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/screens/help.md b/docs/screens/help.md index 3e6c7223..7dbbcc5a 100644 --- a/docs/screens/help.md +++ b/docs/screens/help.md @@ -4,14 +4,14 @@ The Help screen provides users with access to frequently asked questions (FAQs), options to submit feedback, and links to important information like the app's privacy policy, terms of service, and open-source licenses. ## Structure -The Help screen is implemented as an `Activity` that hosts different sections using `PreferenceFragmentCompat`: -- **`HelpActivity`**: The main activity for this screen. It manages the layout and hosts the fragments. -- **`FaqFragment`**: Displays a list of frequently asked questions, loaded from `R.xml.preferences_faq`. +The Help screen is implemented as an `Activity` that composes different sections: +- **`HelpActivity`**: The main activity for this screen. It manages the layout, renders the FAQ list, and hosts the feedback fragment. +- **FAQ list**: Built dynamically inside the activity by inflating a shared item layout for each question/answer pair. - **`FeedbackFragment`**: Contains preferences related to user feedback, including an option to rate the app. This is loaded from `R.xml.preferences_feedback`. ## Features The Help screen offers the following functionalities, accessible primarily through an options menu: -- **View FAQs**: Users can browse a list of common questions and answers. +- **View FAQs**: Users can browse a list of common questions and answers, expanding items on demand to reveal the detailed responses. - **Provide Feedback**: Users can initiate a review flow or be directed to the Google Play Store to leave a review. - **View in Google Play**: Opens the app's listing on the Google Play Store. - **Version Info**: Displays a dialog with the app's name, version, and copyright information.