From 1a4ae7a0ac2e8d575443f22b3c30e97ae099df7d Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 11:52:45 +0300 Subject: [PATCH 01/10] Improve onboarding with data consent and completion screens --- .../ui/screens/onboarding/DataFragment.java | 17 +++++++ .../ui/screens/onboarding/DoneFragment.java | 31 ++++++++++++ .../onboarding/OnboardingActivity.java | 26 +++------- .../onboarding/OnboardingViewModel.java | 4 ++ .../res/layout/fragment_onboarding_data.xml | 39 +++++++++++++-- .../res/layout/fragment_onboarding_done.xml | 48 +++++++++++++++++++ app/src/main/res/values/strings.xml | 5 ++ 7 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java create mode 100644 app/src/main/res/layout/fragment_onboarding_done.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java index 0a1e8645..eeea53fd 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java @@ -1,5 +1,7 @@ package com.d4rk.androidtutorials.java.ui.screens.onboarding; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -28,6 +30,21 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); + + binding.switchCrashlytics.setOnCheckedChangeListener((buttonView, isChecked) -> { + binding.textDetails.setVisibility(isChecked ? View.VISIBLE : View.GONE); + viewModel.setCrashlyticsEnabled(isChecked); + }); + + binding.linkPrivacy.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("https://d4rk7355608.github.io/profile/#privacy-policy-apps")); + startActivity(intent); + }); + } + + public void saveSelection() { + viewModel.setCrashlyticsEnabled(binding.switchCrashlytics.isChecked()); } @Override diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java new file mode 100644 index 00000000..2e837633 --- /dev/null +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java @@ -0,0 +1,31 @@ +package com.d4rk.androidtutorials.java.ui.screens.onboarding; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingDoneBinding; + +public class DoneFragment extends Fragment { + + private FragmentOnboardingDoneBinding binding; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragmentOnboardingDoneBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java index 70154219..99b08896 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java @@ -46,12 +46,8 @@ public void onPageSelected(int position) { Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + currentPosition); if (fragment instanceof ThemeFragment) { ((ThemeFragment) fragment).saveSelection(); - } else if (fragment instanceof StartPageFragment) { - ((StartPageFragment) fragment).saveSelection(); - } else if (fragment instanceof BottomLabelsFragment) { - ((BottomLabelsFragment) fragment).saveSelection(); - } else if (fragment instanceof FontFragment) { - ((FontFragment) fragment).saveSelection(); + } else if (fragment instanceof DataFragment) { + ((DataFragment) fragment).saveSelection(); } } currentPosition = position; @@ -102,12 +98,8 @@ public void onTabReselected(TabLayout.Tab tab) { Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + current); if (fragment instanceof ThemeFragment) { ((ThemeFragment) fragment).saveSelection(); - } else if (fragment instanceof StartPageFragment) { - ((StartPageFragment) fragment).saveSelection(); - } else if (fragment instanceof BottomLabelsFragment) { - ((BottomLabelsFragment) fragment).saveSelection(); - } else if (fragment instanceof FontFragment) { - ((FontFragment) fragment).saveSelection(); + } else if (fragment instanceof DataFragment) { + ((DataFragment) fragment).saveSelection(); } if (current < adapter.getItemCount() - 1) { @@ -148,19 +140,15 @@ public Fragment createFragment(int position) { case 0: return new ThemeFragment(); case 1: - return new StartPageFragment(); - case 2: - return new BottomLabelsFragment(); - case 3: - return new FontFragment(); - default: return new DataFragment(); + default: + return new DoneFragment(); } } @Override public int getItemCount() { - return 5; + return 3; } } } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java index dd02e95f..6646253e 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java @@ -41,6 +41,10 @@ public void setMonospaceFont(String value) { prefs.edit().putString(context.getString(R.string.key_monospace_font), value).apply(); } + public void setCrashlyticsEnabled(boolean enabled) { + prefs.edit().putBoolean(context.getString(R.string.key_firebase_crashlytics), enabled).apply(); + } + public void markOnboardingComplete() { prefs.edit().putBoolean(context.getString(R.string.key_onboarding_complete), true).apply(); } diff --git a/app/src/main/res/layout/fragment_onboarding_data.xml b/app/src/main/res/layout/fragment_onboarding_data.xml index 798968a1..208eccaf 100644 --- a/app/src/main/res/layout/fragment_onboarding_data.xml +++ b/app/src/main/res/layout/fragment_onboarding_data.xml @@ -16,10 +16,43 @@ android:padding="16dp" app:cardElevation="0dp"> - + android:orientation="vertical"> + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_onboarding_done.xml b/app/src/main/res/layout/fragment_onboarding_done.xml new file mode 100644 index 00000000..1b79d96b --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_done.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a6ea224..33109769 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -343,6 +343,11 @@ Back Bottom navigation labels We collect data to improve your experience. + Help Us Improve + Allow anonymous usage and crash reports + Data reporting is active. + You're All Set! + Thanks for customizing your experience. Enjoy exploring the app. Allows the app to access and modify the device\'s notification policy, controlling how and when notifications are displayed to the user and providing custom notification management features. Allows the app to create and use services that run in the foreground, giving them priority over other background processes and improving performance and reliability. Set application language. From c68536892bb05afc3ea2a19c8279623100951694 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 12:27:21 +0300 Subject: [PATCH 02/10] Refine theme selection UI and add tools namespace --- .../ui/screens/onboarding/ThemeFragment.java | 2 - .../res/layout/fragment_onboarding_theme.xml | 106 +++++++++--------- app/src/main/res/values-bg-rBG/strings.xml | 2 +- app/src/main/res/values-fil-rPH/strings.xml | 2 +- app/src/main/res/values-hu-rHU/strings.xml | 2 +- app/src/main/res/values-in-rID/strings.xml | 2 +- app/src/main/res/values-ko-rKR/strings.xml | 2 +- app/src/main/res/values-pl-rPL/strings.xml | 2 +- app/src/main/res/values-ru-rRU/strings.xml | 2 +- app/src/main/res/values-sv-rSE/strings.xml | 2 +- app/src/main/res/values-th-rTH/strings.xml | 2 +- app/src/main/res/values-tr-rTR/strings.xml | 2 +- app/src/main/res/values-uk-rUA/strings.xml | 2 +- app/src/main/res/values-vi-rVN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 8 ++ 16 files changed, 75 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index 93c46b94..7df7a60e 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -39,8 +39,6 @@ public void saveSelection() { value = values[1]; } else if (checkedId == R.id.radio_dark) { value = values[2]; - } else if (checkedId == R.id.radio_auto_battery) { - value = values[3]; } viewModel.setTheme(value); } diff --git a/app/src/main/res/layout/fragment_onboarding_theme.xml b/app/src/main/res/layout/fragment_onboarding_theme.xml index f57137e2..1f62e7a5 100644 --- a/app/src/main/res/layout/fragment_onboarding_theme.xml +++ b/app/src/main/res/layout/fragment_onboarding_theme.xml @@ -1,65 +1,67 @@ - + android:layout_height="match_parent" + android:padding="24dp" + tools:theme="@style/Theme.Material3.Dark"> - + + + + + app:layout_constraintTop_toBottomOf="@id/description" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> - - - - - + android:checked="true" + android:text="@string/follow_system" + android:layout_marginBottom="16dp" /> - - - - - + - + - - - - + - - + diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 748376a6..eec5ed78 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -1,5 +1,5 @@ - + Преглед на изображението Научете как да създавате прости Java приложения в Android Studio. 📱 Налична е нова актуализация. diff --git a/app/src/main/res/values-fil-rPH/strings.xml b/app/src/main/res/values-fil-rPH/strings.xml index 29d093d5..1741f20e 100644 --- a/app/src/main/res/values-fil-rPH/strings.xml +++ b/app/src/main/res/values-fil-rPH/strings.xml @@ -1,5 +1,5 @@ - + Paunang tingin sa image view Alamin kung paano gumawa ng mga simpleng Java app sa Android Studio. 📱 May bagong update na available. diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 1dc23138..42f914ac 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -1,5 +1,5 @@ - + Képnézet előnézete Tanuld meg, hogyan készíts egyszerű Java alkalmazásokat az Android Studioban. 📱 Új frissítés érhető el. diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index 1ba113fc..a02f9ea7 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -1,5 +1,5 @@ - + Pratinjau tampilan gambar Pelajari cara membuat aplikasi sederhana di Android Studio. 📱 Pembaruan baru tersedia. diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index c173f126..3e01a683 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -1,5 +1,5 @@ - + 이미지 뷰 미리보기 Android Studio에서 간단한 Java 앱을 만드는 방법을 배워보세요. 📱 새로운 업데이트가 있습니다. diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 5f0a6deb..1ec3740c 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -1,5 +1,5 @@ - + Podgląd widoku obrazu Naucz się tworzyć proste aplikacje Java w Android Studio. 📱 Dostępna jest nowa aktualizacja. diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 84d1b907..5501e0fa 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -1,5 +1,5 @@ - + Предпросмотр изображения Узнайте, как создавать простые приложения в Android Studio. 📱 Доступно новое обновление. diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 67da5496..89b7dba5 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -1,5 +1,5 @@ - + Förhandsgranskning av bildvy Lär dig hur man skapar enkla Java-appar i Android Studio. 📱 En ny uppdatering finns tillgänglig. diff --git a/app/src/main/res/values-th-rTH/strings.xml b/app/src/main/res/values-th-rTH/strings.xml index f1c60142..cbc7f638 100644 --- a/app/src/main/res/values-th-rTH/strings.xml +++ b/app/src/main/res/values-th-rTH/strings.xml @@ -1,5 +1,5 @@ - + ดูตัวอย่างมุมมองภาพ เรียนรู้วิธีสร้างแอป Java ง่ายๆ ใน Android Studio 📱 มีการอัปเดตใหม่. diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 97d2b1da..7eb23b1b 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -1,5 +1,5 @@ - + Görüntü görünümü önizlemesi Android Studio\'da basit Java uygulamaları yapmayı öğrenin. 📱 Yeni bir güncelleme mevcut. diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 781f0bf8..691a6d9f 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -1,5 +1,5 @@ - + Попередній перегляд зображення Дізнайтеся, як створювати прості програми на Java в Android Studio. 📱 Доступне нове оновлення. diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index 46634052..e3b29446 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -1,5 +1,5 @@ - + Xem trước khung nhìn hình ảnh Học cách tạo các ứng dụng Java đơn giản trong Android Studio. 📱 Có bản cập nhật mới. diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b897a104..09e9aa9b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,5 +1,5 @@ - + 圖片檢視預覽 學習如何在 Android Studio 中製作簡單的 Java 應用程式。 📱 有可用的新更新! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33109769..25a64191 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -343,6 +343,14 @@ Back Bottom navigation labels We collect data to improve your experience. + Skip + Choose your style + Select how you'd like the app to look. + Bright and energetic. + Easy on the eyes. + Matches your device. + Choose whether to follow the system theme or override it. + Automatically switches to dark mode at night and when battery saver is on. Help Us Improve Allow anonymous usage and crash reports Data reporting is active. From ef214f2b3ae8a9de0cf01145185e4d8835051029 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 12:27:26 +0300 Subject: [PATCH 03/10] Add default tab selection screen --- .../onboarding/OnboardingActivity.java | 8 +- .../screens/onboarding/StartPageFragment.java | 4 + .../layout/fragment_onboarding_start_page.xml | 197 +++++++++++++++--- app/src/main/res/values/strings.xml | 4 + 4 files changed, 184 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java index 99b08896..6051f6c9 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java @@ -46,6 +46,8 @@ public void onPageSelected(int position) { Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + currentPosition); if (fragment instanceof ThemeFragment) { ((ThemeFragment) fragment).saveSelection(); + } else if (fragment instanceof StartPageFragment) { + ((StartPageFragment) fragment).saveSelection(); } else if (fragment instanceof DataFragment) { ((DataFragment) fragment).saveSelection(); } @@ -98,6 +100,8 @@ public void onTabReselected(TabLayout.Tab tab) { Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + current); if (fragment instanceof ThemeFragment) { ((ThemeFragment) fragment).saveSelection(); + } else if (fragment instanceof StartPageFragment) { + ((StartPageFragment) fragment).saveSelection(); } else if (fragment instanceof DataFragment) { ((DataFragment) fragment).saveSelection(); } @@ -140,6 +144,8 @@ public Fragment createFragment(int position) { case 0: return new ThemeFragment(); case 1: + return new StartPageFragment(); + case 2: return new DataFragment(); default: return new DoneFragment(); @@ -148,7 +154,7 @@ public Fragment createFragment(int position) { @Override public int getItemCount() { - return 3; + return 4; } } } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java index 63e60ae2..98cfabbd 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java @@ -29,6 +29,10 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); + + binding.cardHome.setOnClickListener(v -> binding.radioHome.setChecked(true)); + binding.cardAndroidStudio.setOnClickListener(v -> binding.radioAndroidStudio.setChecked(true)); + binding.cardAbout.setOnClickListener(v -> binding.radioAbout.setChecked(true)); } public void saveSelection() { diff --git a/app/src/main/res/layout/fragment_onboarding_start_page.xml b/app/src/main/res/layout/fragment_onboarding_start_page.xml index d274ce73..98b33236 100644 --- a/app/src/main/res/layout/fragment_onboarding_start_page.xml +++ b/app/src/main/res/layout/fragment_onboarding_start_page.xml @@ -1,59 +1,200 @@ - + android:layout_height="match_parent" + android:fillViewport="true"> - - + + android:text="@string/default_tab" + android:textAppearance="?attr/textAppearanceHeadlineMedium" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> - + + + + + + + + app:cardElevation="0dp"> - + android:orientation="horizontal" + android:gravity="center_vertical" + android:padding="16dp"> - + + + + + + + + + android:checked="true"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginStart="16dp" + android:layout_weight="1" + android:orientation="vertical"> + + + + + - - - + android:layout_height="wrap_content"/> + + + + - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25a64191..37da8436 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,6 +254,10 @@ Dark mode Auto battery dark mode Default tab + Select which tab opens when the app starts. + Latest tutorials and updates. + Tools and tips for Android Studio. + Information about this app. Bottom navigation bar labels Labeled Selected %1$s From ac4d673e36f4ed5e4e5bff5d6e9c758349177057 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 12:45:03 +0300 Subject: [PATCH 04/10] Refine onboarding layouts and update links --- .../java/ui/screens/about/AboutFragment.java | 2 +- .../lessons/views/web/WebViewActivity.java | 2 +- .../java/ui/screens/help/HelpActivity.java | 4 +- .../ui/screens/onboarding/DataFragment.java | 2 +- .../screens/onboarding/StartPageFragment.java | 39 +++- .../ui/screens/onboarding/ThemeFragment.java | 37 +++- .../ui/screens/startup/StartupActivity.java | 2 +- .../layout/fragment_onboarding_selection.xml | 121 +++++++++++ .../layout/fragment_onboarding_start_page.xml | 200 ------------------ .../res/layout/fragment_onboarding_theme.xml | 67 ------ .../res/layout/item_onboarding_option.xml | 62 ++++++ app/src/main/res/raw/text_webview_java.txt | 2 +- app/src/main/res/values/strings.xml | 28 +-- app/src/main/res/xml/preferences_settings.xml | 6 +- 14 files changed, 268 insertions(+), 306 deletions(-) create mode 100644 app/src/main/res/layout/fragment_onboarding_selection.xml delete mode 100644 app/src/main/res/layout/fragment_onboarding_start_page.xml delete mode 100644 app/src/main/res/layout/fragment_onboarding_theme.xml create mode 100644 app/src/main/res/layout/item_onboarding_option.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/about/AboutFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/about/AboutFragment.java index 8d905467..a9f45463 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/about/AboutFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/about/AboutFragment.java @@ -67,7 +67,7 @@ public android.view.View onCreateView(@NonNull android.view.LayoutInflater infla }); binding.imageViewAppIcon.setOnClickListener( - v -> openUrl("https://d4rk7355608.github.io/home/")); + v -> openUrl("https://mihaicristiancondrea.github.io/profile")); binding.chipGoogleDev.setOnClickListener( v -> openUrl("https://g.dev/D4rK7355608")); binding.chipYoutube.setOnClickListener( diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/views/web/WebViewActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/views/web/WebViewActivity.java index bce77bbb..75edff7a 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/views/web/WebViewActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/views/web/WebViewActivity.java @@ -36,7 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { @SuppressLint("SetJavaScriptEnabled") private void setupWebView() { WebView webView = binding.webView; - webView.loadUrl("https://d4rk7355608.github.io/profile/#home"); + webView.loadUrl("https://mihaicristiancondrea.github.io/profile/"); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); 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 920d98a3..327e14a1 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 @@ -76,10 +76,10 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { openLink("https://play.google.com/apps/testing/" + getPackageName()); return true; } else if (itemId == R.id.terms_of_service) { - openLink("https://d4rk7355608.github.io/profile/#terms-of-service-apps"); + openLink("https://mihaicristiancondrea.github.io/profile/#terms-of-service-end-user-software"); return true; } else if (itemId == R.id.privacy_policy) { - openLink("https://d4rk7355608.github.io/profile/#privacy-policy-apps"); + openLink("https://mihaicristiancondrea.github.io/profile/#privacy-policy-end-user-software"); return true; } else if (itemId == R.id.oss) { OpenSourceLicensesUtils.loadHtmlData(this, (changelogHtml, eulaHtml) -> openLicensesScreen(this, eulaHtml, changelogHtml)); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java index eeea53fd..7f9c1daa 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java @@ -38,7 +38,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.linkPrivacy.setOnClickListener(v -> { Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse("https://d4rk7355608.github.io/profile/#privacy-policy-apps")); + Uri.parse("https://mihaicristiancondrea.github.io/profile/#privacy-policy-end-user-software")); startActivity(intent); }); } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java index 98cfabbd..64e8e2ab 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java @@ -7,21 +7,35 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import com.d4rk.androidtutorials.java.R; -import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingStartPageBinding; +import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingSelectionBinding; public class StartPageFragment extends Fragment { - private FragmentOnboardingStartPageBinding binding; + private FragmentOnboardingSelectionBinding binding; private OnboardingViewModel viewModel; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - binding = FragmentOnboardingStartPageBinding.inflate(inflater, container, false); + binding = FragmentOnboardingSelectionBinding.inflate(inflater, container, false); + + binding.setTitle(getString(R.string.default_tab)); + binding.setDescription(getString(R.string.default_tab_description)); + binding.setFirstIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_home)); + binding.setFirstTitle(getString(R.string.home)); + binding.setFirstDescription(getString(R.string.home_description)); + binding.setSecondIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_android_sdk)); + binding.setSecondTitle(getString(R.string.android_studio)); + binding.setSecondDescription(getString(R.string.android_studio_description)); + binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_info)); + binding.setThirdTitle(getString(R.string.about)); + binding.setThirdDescription(getString(R.string.about_description)); + return binding.getRoot(); } @@ -30,18 +44,25 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); - binding.cardHome.setOnClickListener(v -> binding.radioHome.setChecked(true)); - binding.cardAndroidStudio.setOnClickListener(v -> binding.radioAndroidStudio.setChecked(true)); - binding.cardAbout.setOnClickListener(v -> binding.radioAbout.setChecked(true)); + // Assign unique IDs to each radio button for proper RadioGroup handling + binding.optionFirst.radioButton.setId(View.generateViewId()); + binding.optionSecond.radioButton.setId(View.generateViewId()); + binding.optionThird.radioButton.setId(View.generateViewId()); + + // Default selection + binding.optionFirst.radioButton.setChecked(true); + + binding.cardFirst.setOnClickListener(v -> binding.optionFirst.radioButton.setChecked(true)); + binding.cardSecond.setOnClickListener(v -> binding.optionSecond.radioButton.setChecked(true)); + binding.cardThird.setOnClickListener(v -> binding.optionThird.radioButton.setChecked(true)); } public void saveSelection() { - int checkedId = binding.startPageGroup.getCheckedRadioButtonId(); String[] values = getResources().getStringArray(R.array.preference_default_tab_values); String value = values[0]; - if (checkedId == R.id.radio_android_studio) { + if (binding.optionSecond.radioButton.isChecked()) { value = values[1]; - } else if (checkedId == R.id.radio_about) { + } else if (binding.optionThird.radioButton.isChecked()) { value = values[2]; } viewModel.setDefaultTab(value); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index 7df7a60e..f7bffc57 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -7,21 +7,35 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import com.d4rk.androidtutorials.java.R; -import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingThemeBinding; +import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingSelectionBinding; public class ThemeFragment extends Fragment { - private FragmentOnboardingThemeBinding binding; + private FragmentOnboardingSelectionBinding binding; private OnboardingViewModel viewModel; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - binding = FragmentOnboardingThemeBinding.inflate(inflater, container, false); + binding = FragmentOnboardingSelectionBinding.inflate(inflater, container, false); + + binding.setTitle(getString(R.string.choose_your_style)); + binding.setDescription(getString(R.string.select_how_you_d_like_the_app_to_look)); + binding.setFirstIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_theme)); + binding.setFirstTitle(getString(R.string.light_mode)); + binding.setFirstDescription(getString(R.string.light_mode_description)); + binding.setSecondIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_about)); + binding.setSecondTitle(getString(R.string.dark_mode)); + binding.setSecondDescription(getString(R.string.dark_mode_description)); + binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_android)); + binding.setThirdTitle(getString(R.string.follow_system_mode)); + binding.setThirdDescription(getString(R.string.follow_system_mode_description)); + return binding.getRoot(); } @@ -29,15 +43,26 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); + + // Assign unique IDs to each radio button + binding.optionFirst.radioButton.setId(View.generateViewId()); + binding.optionSecond.radioButton.setId(View.generateViewId()); + binding.optionThird.radioButton.setId(View.generateViewId()); + + // Default selection + binding.optionThird.radioButton.setChecked(true); + + binding.cardFirst.setOnClickListener(v -> binding.optionFirst.radioButton.setChecked(true)); + binding.cardSecond.setOnClickListener(v -> binding.optionSecond.radioButton.setChecked(true)); + binding.cardThird.setOnClickListener(v -> binding.optionThird.radioButton.setChecked(true)); } public void saveSelection() { - int checkedId = binding.themeGroup.getCheckedRadioButtonId(); String[] values = getResources().getStringArray(R.array.preference_theme_values); String value = values[0]; - if (checkedId == R.id.radio_light) { + if (binding.optionFirst.radioButton.isChecked()) { value = values[1]; - } else if (checkedId == R.id.radio_dark) { + } else if (binding.optionSecond.radioButton.isChecked()) { value = values[2]; } viewModel.setTheme(value); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/StartupActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/StartupActivity.java index 7b75b49b..0bc2ccd8 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/StartupActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/StartupActivity.java @@ -36,7 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { binding.buttonBrowsePrivacyPolicyAndTermsOfService.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("https://d4rk7355608.github.io/profile/#privacy-policy-apps"))) + Uri.parse("https://mihaicristiancondrea.github.io/profile/#privacy-policy-end-user-software"))) ); binding.floatingButtonAgree.setOnClickListener(v -> { diff --git a/app/src/main/res/layout/fragment_onboarding_selection.xml b/app/src/main/res/layout/fragment_onboarding_selection.xml new file mode 100644 index 00000000..e9bb7d17 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_selection.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_onboarding_start_page.xml b/app/src/main/res/layout/fragment_onboarding_start_page.xml deleted file mode 100644 index 98b33236..00000000 --- a/app/src/main/res/layout/fragment_onboarding_start_page.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_theme.xml b/app/src/main/res/layout/fragment_onboarding_theme.xml deleted file mode 100644 index 1f62e7a5..00000000 --- a/app/src/main/res/layout/fragment_onboarding_theme.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_onboarding_option.xml b/app/src/main/res/layout/item_onboarding_option.xml new file mode 100644 index 00000000..6a2c0689 --- /dev/null +++ b/app/src/main/res/layout/item_onboarding_option.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/raw/text_webview_java.txt b/app/src/main/res/raw/text_webview_java.txt index 5faf61d2..837b041f 100644 --- a/app/src/main/res/raw/text_webview_java.txt +++ b/app/src/main/res/raw/text_webview_java.txt @@ -24,6 +24,6 @@ public class MainActivity extends AppCompatActivity { webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setJavaScriptCanOpenWindowsAutomatically(true); - binding.webView.loadUrl("https://d4rk7355608.github.io/profile/#home"); + binding.webView.loadUrl("https://mihaicristiancondrea.github.io/profile/"); } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37da8436..4afbd1fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,10 +254,10 @@ Dark mode Auto battery dark mode Default tab - Select which tab opens when the app starts. - Latest tutorials and updates. - Tools and tips for Android Studio. - Information about this app. + Select the tab that opens at launch + Top stories and recent updates + Android Studio tools, tips, and the full tutorial catalog + App details, version, and credits Bottom navigation bar labels Labeled Selected %1$s @@ -349,17 +349,17 @@ We collect data to improve your experience. Skip Choose your style - Select how you'd like the app to look. - Bright and energetic. - Easy on the eyes. - Matches your device. - Choose whether to follow the system theme or override it. - Automatically switches to dark mode at night and when battery saver is on. + Choose how the app looks + Bright, clean appearance + Comfortable in low light + Follows your device theme + Use the system theme or choose a custom one + Switches to dark at night or when Battery Saver is on Help Us Improve - Allow anonymous usage and crash reports - Data reporting is active. - You're All Set! - Thanks for customizing your experience. Enjoy exploring the app. + Share anonymous usage and crash reports + Reporting is enabled + You\'re All Set + Thanks for setting up your preferences. Enjoy the app. Allows the app to access and modify the device\'s notification policy, controlling how and when notifications are displayed to the user and providing custom notification management features. Allows the app to create and use services that run in the foreground, giving them priority over other background processes and improving performance and reliability. Set application language. diff --git a/app/src/main/res/xml/preferences_settings.xml b/app/src/main/res/xml/preferences_settings.xml index 0a02bec5..a39bf3a0 100644 --- a/app/src/main/res/xml/preferences_settings.xml +++ b/app/src/main/res/xml/preferences_settings.xml @@ -75,21 +75,21 @@ app:title="@string/privacy_policy"> + android:data="https://mihaicristiancondrea.github.io/profile/#privacy-policy-end-user-software" /> + android:data="https://mihaicristiancondrea.github.io/profile/#terms-of-service-end-user-software" /> + android:data="https://mihaicristiancondrea.github.io/profile/#code-of-conduct" /> Date: Fri, 12 Sep 2025 13:11:14 +0300 Subject: [PATCH 05/10] Refine onboarding selection layout and enable data binding --- app/build.gradle | 1 + .../screens/onboarding/StartPageFragment.java | 2 +- .../ui/screens/onboarding/ThemeFragment.java | 2 +- .../layout/fragment_onboarding_selection.xml | 96 +++++++++++++------ 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 05282b53..764d8b6d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,6 +42,7 @@ android { buildFeatures { viewBinding true + dataBinding true buildConfig true } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java index 64e8e2ab..d0dad53f 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java @@ -32,7 +32,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.setSecondIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_android_sdk)); binding.setSecondTitle(getString(R.string.android_studio)); binding.setSecondDescription(getString(R.string.android_studio_description)); - binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_info)); + binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_about)); binding.setThirdTitle(getString(R.string.about)); binding.setThirdDescription(getString(R.string.about_description)); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index f7bffc57..f83f487b 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -33,7 +33,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.setSecondTitle(getString(R.string.dark_mode)); binding.setSecondDescription(getString(R.string.dark_mode_description)); binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_android)); - binding.setThirdTitle(getString(R.string.follow_system_mode)); + binding.setThirdTitle(getString(R.string.follow_system)); binding.setThirdDescription(getString(R.string.follow_system_mode_description)); return binding.getRoot(); diff --git a/app/src/main/res/layout/fragment_onboarding_selection.xml b/app/src/main/res/layout/fragment_onboarding_selection.xml index e9bb7d17..3d1e9f7c 100644 --- a/app/src/main/res/layout/fragment_onboarding_selection.xml +++ b/app/src/main/res/layout/fragment_onboarding_selection.xml @@ -1,20 +1,52 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/title" /> + + app:title="@{firstTitle}" /> + android:layout_marginTop="2dp" + app:cardCornerRadius="4dp"> + + app:title="@{secondTitle}" /> + android:layout_marginTop="2dp" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.CardViewBottomRounded"> + + app:title="@{thirdTitle}" /> @@ -111,11 +145,11 @@ android:id="@+id/page_indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" android:layout_marginBottom="16dp" + android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> From 31c0d531d91698222ded882b95951b12c67b184b Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 18:11:31 +0300 Subject: [PATCH 06/10] Add icons for data consent screen --- .../ui/screens/onboarding/DataFragment.java | 6 +- .../screens/onboarding/StartPageFragment.java | 20 +++-- .../ui/screens/onboarding/ThemeFragment.java | 20 +++-- .../main/res/drawable/ic_arrow_forward.xml | 9 ++ app/src/main/res/drawable/ic_security.xml | 9 ++ .../res/layout/fragment_onboarding_data.xml | 85 ++++++++++++++----- .../res/layout/item_onboarding_option.xml | 8 +- app/src/main/res/values/strings.xml | 10 ++- 8 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 app/src/main/res/drawable/ic_arrow_forward.xml create mode 100644 app/src/main/res/drawable/ic_security.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java index 7f9c1daa..ce23860b 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java @@ -31,10 +31,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); - binding.switchCrashlytics.setOnCheckedChangeListener((buttonView, isChecked) -> { - binding.textDetails.setVisibility(isChecked ? View.VISIBLE : View.GONE); - viewModel.setCrashlyticsEnabled(isChecked); - }); + binding.switchCrashlytics.setOnCheckedChangeListener((buttonView, isChecked) -> + viewModel.setCrashlyticsEnabled(isChecked)); binding.linkPrivacy.setOnClickListener(v -> { Intent intent = new Intent(Intent.ACTION_VIEW, diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java index d0dad53f..2c392d0e 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/StartPageFragment.java @@ -44,17 +44,25 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); - // Assign unique IDs to each radio button for proper RadioGroup handling binding.optionFirst.radioButton.setId(View.generateViewId()); binding.optionSecond.radioButton.setId(View.generateViewId()); binding.optionThird.radioButton.setId(View.generateViewId()); - // Default selection - binding.optionFirst.radioButton.setChecked(true); + selectOption(0); - binding.cardFirst.setOnClickListener(v -> binding.optionFirst.radioButton.setChecked(true)); - binding.cardSecond.setOnClickListener(v -> binding.optionSecond.radioButton.setChecked(true)); - binding.cardThird.setOnClickListener(v -> binding.optionThird.radioButton.setChecked(true)); + binding.cardFirst.setOnClickListener(v -> selectOption(0)); + binding.cardSecond.setOnClickListener(v -> selectOption(1)); + binding.cardThird.setOnClickListener(v -> selectOption(2)); + + binding.optionFirst.radioButton.setOnClickListener(v -> selectOption(0)); + binding.optionSecond.radioButton.setOnClickListener(v -> selectOption(1)); + binding.optionThird.radioButton.setOnClickListener(v -> selectOption(2)); + } + + private void selectOption(int index) { + binding.optionFirst.radioButton.setChecked(index == 0); + binding.optionSecond.radioButton.setChecked(index == 1); + binding.optionThird.radioButton.setChecked(index == 2); } public void saveSelection() { diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index f83f487b..fd790e24 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -44,17 +44,25 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); - // Assign unique IDs to each radio button binding.optionFirst.radioButton.setId(View.generateViewId()); binding.optionSecond.radioButton.setId(View.generateViewId()); binding.optionThird.radioButton.setId(View.generateViewId()); - // Default selection - binding.optionThird.radioButton.setChecked(true); + selectOption(2); - binding.cardFirst.setOnClickListener(v -> binding.optionFirst.radioButton.setChecked(true)); - binding.cardSecond.setOnClickListener(v -> binding.optionSecond.radioButton.setChecked(true)); - binding.cardThird.setOnClickListener(v -> binding.optionThird.radioButton.setChecked(true)); + binding.cardFirst.setOnClickListener(v -> selectOption(0)); + binding.cardSecond.setOnClickListener(v -> selectOption(1)); + binding.cardThird.setOnClickListener(v -> selectOption(2)); + + binding.optionFirst.radioButton.setOnClickListener(v -> selectOption(0)); + binding.optionSecond.radioButton.setOnClickListener(v -> selectOption(1)); + binding.optionThird.radioButton.setOnClickListener(v -> selectOption(2)); + } + + private void selectOption(int index) { + binding.optionFirst.radioButton.setChecked(index == 0); + binding.optionSecond.radioButton.setChecked(index == 1); + binding.optionThird.radioButton.setChecked(index == 2); } public void saveSelection() { diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml new file mode 100644 index 00000000..c4688d61 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml new file mode 100644 index 00000000..332ad012 --- /dev/null +++ b/app/src/main/res/drawable/ic_security.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_onboarding_data.xml b/app/src/main/res/layout/fragment_onboarding_data.xml index 208eccaf..91190d5a 100644 --- a/app/src/main/res/layout/fragment_onboarding_data.xml +++ b/app/src/main/res/layout/fragment_onboarding_data.xml @@ -2,7 +2,8 @@ + android:layout_height="match_parent" + android:background="?attr/colorSurfaceContainer"> + android:orientation="vertical" + android:padding="24dp"> - + - + android:layout_gravity="center_horizontal" + android:layout_marginTop="16dp" + android:text="@string/onboarding_data_title" + android:textAlignment="center" /> + android:text="@string/onboarding_data_subtitle" + android:textAlignment="center" + android:textColor="?attr/colorOnSurfaceVariant" /> - + android:layout_marginTop="24dp" + android:text="@string/onboarding_data_toggle" + android:textAppearance="@style/TextAppearance.Material3.TitleMedium" /> + + + + + + + + + diff --git a/app/src/main/res/layout/item_onboarding_option.xml b/app/src/main/res/layout/item_onboarding_option.xml index 6a2c0689..83cee11c 100644 --- a/app/src/main/res/layout/item_onboarding_option.xml +++ b/app/src/main/res/layout/item_onboarding_option.xml @@ -13,7 +13,7 @@ android:layout_height="wrap_content" android:padding="20dp"> - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4afbd1fb..8fd16be0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -355,9 +355,13 @@ Follows your device theme Use the system theme or choose a custom one Switches to dark at night or when Battery Saver is on - Help Us Improve - Share anonymous usage and crash reports - Reporting is enabled + Help Us Improve Your Experience + Help us make the app better for you by sharing anonymous crash reports and usage statistics. + Enable Crash Reporting + By enabling this, you help us identify and fix bugs faster. We do not collect any personal information. + Read our Privacy Policy + Privacy Icon + Arrow Icon You\'re All Set Thanks for setting up your preferences. Enjoy the app. Allows the app to access and modify the device\'s notification policy, controlling how and when notifications are displayed to the user and providing custom notification management features. From d7b6a47e9470edd5bd290fce480c7ba7f27d2d09 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 18:33:00 +0300 Subject: [PATCH 07/10] Polish onboarding UI and consent handling --- .../ui/screens/onboarding/DoneFragment.java | 15 +++++ .../onboarding/OnboardingActivity.java | 31 ++++++++- app/src/main/res/drawable/ic_arrow_back.xml | 10 +++ app/src/main/res/drawable/ic_close.xml | 10 +++ .../main/res/layout/activity_onboarding.xml | 24 ++++++- .../res/layout/fragment_onboarding_data.xml | 3 +- .../res/layout/fragment_onboarding_done.xml | 64 +++++++++++++------ .../res/layout/item_onboarding_option.xml | 10 ++- app/src/main/res/values/strings.xml | 8 ++- 9 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 app/src/main/res/drawable/ic_arrow_back.xml create mode 100644 app/src/main/res/drawable/ic_close.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java index 2e837633..69093a4f 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java @@ -9,11 +9,14 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingDoneBinding; public class DoneFragment extends Fragment { private FragmentOnboardingDoneBinding binding; + private OnboardingViewModel viewModel; @Nullable @Override @@ -23,6 +26,18 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return binding.getRoot(); } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); + binding.buttonGetStarted.setOnClickListener(v -> { + viewModel.markOnboardingComplete(); + if (getActivity() instanceof OnboardingActivity) { + ((OnboardingActivity) getActivity()).finishOnboarding(); + } + }); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java index 6051f6c9..2bacf844 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java @@ -1,11 +1,14 @@ package com.d4rk.androidtutorials.java.ui.screens.onboarding; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.ImageView; +import android.widget.LinearLayout; import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; @@ -14,6 +17,8 @@ import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.databinding.ActivityOnboardingBinding; import com.d4rk.androidtutorials.java.ui.screens.main.MainActivity; +import com.d4rk.androidtutorials.java.ui.screens.startup.dialogs.ConsentDialogFragment; +import com.d4rk.androidtutorials.java.utils.ConsentUtils; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; @@ -38,6 +43,14 @@ protected void onCreate(Bundle savedInstanceState) { adapter = new OnboardingPagerAdapter(this); binding.viewPager.setAdapter(adapter); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String keyAnalytics = getString(R.string.key_consent_analytics); + if (!prefs.contains(keyAnalytics)) { + ConsentDialogFragment dialog = new ConsentDialogFragment(); + dialog.setConsentListener((a,b,c,d) -> ConsentUtils.updateFirebaseConsent(this, a,b,c,d)); + dialog.show(getSupportFragmentManager(), "consent"); + } + binding.viewPager.registerOnPageChangeCallback(new androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { @@ -59,6 +72,11 @@ public void onPageSelected(int position) { new TabLayoutMediator(binding.tabIndicator, binding.viewPager, (tab, position) -> { ImageView dot = new ImageView(this); dot.setImageResource(R.drawable.onboarding_dot_unselected); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(8,0,8,0); + dot.setLayoutParams(params); tab.setCustomView(dot); }).attach(); @@ -95,6 +113,11 @@ public void onTabReselected(TabLayout.Tab tab) { } }); + binding.buttonSkip.setOnClickListener(v -> { + viewModel.markOnboardingComplete(); + finishOnboarding(); + }); + binding.buttonNext.setOnClickListener(v -> { int current = binding.viewPager.getCurrentItem(); Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + current); @@ -123,11 +146,15 @@ void finishOnboarding() { } private void updateButtons(int position) { - binding.buttonBack.setVisibility(position == 0 ? View.INVISIBLE : View.VISIBLE); if (position == adapter.getItemCount() - 1) { - binding.buttonNext.setText(R.string.finish); + binding.bottomBar.setVisibility(View.GONE); + binding.buttonSkip.setVisibility(View.GONE); } else { + binding.bottomBar.setVisibility(View.VISIBLE); + binding.buttonSkip.setVisibility(View.VISIBLE); + binding.buttonBack.setVisibility(position == 0 ? View.INVISIBLE : View.VISIBLE); binding.buttonNext.setText(R.string.next); + binding.buttonNext.setIconResource(R.drawable.ic_arrow_forward); } } diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 00000000..261ff5f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..29fee3b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index b7720b68..b431547c 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -5,6 +5,18 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> + + + android:text="@string/back" + app:icon="@drawable/ic_arrow_back" + app:iconGravity="textStart" /> + android:text="@string/next" + app:icon="@drawable/ic_arrow_forward" + app:iconGravity="textStart" /> diff --git a/app/src/main/res/layout/fragment_onboarding_data.xml b/app/src/main/res/layout/fragment_onboarding_data.xml index 91190d5a..e03bd8b1 100644 --- a/app/src/main/res/layout/fragment_onboarding_data.xml +++ b/app/src/main/res/layout/fragment_onboarding_data.xml @@ -2,8 +2,7 @@ + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:padding="24dp"> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + app:cardCornerRadius="24dp" + app:cardElevation="0dp" + app:strokeWidth="1dp"> + android:orientation="vertical" + android:padding="32dp"> + android:id="@+id/success_icon" + android:layout_width="80dp" + android:layout_height="80dp" + android:src="@drawable/ic_check_circle" + android:contentDescription="@string/success_icon" + app:tint="?attr/colorPrimary" /> + android:layout_marginTop="24dp" + android:text="@string/onboarding_done_title" + android:textAlignment="center" /> + android:layout_marginTop="12dp" + android:text="@string/onboarding_done_subtitle" + android:textAlignment="center" + android:textColor="?attr/colorOnSurfaceVariant" /> + + + - + + diff --git a/app/src/main/res/layout/item_onboarding_option.xml b/app/src/main/res/layout/item_onboarding_option.xml index 83cee11c..e7e779ea 100644 --- a/app/src/main/res/layout/item_onboarding_option.xml +++ b/app/src/main/res/layout/item_onboarding_option.xml @@ -11,7 +11,7 @@ + android:padding="16dp"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8fd16be0..c264467c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,7 +254,7 @@ Dark mode Auto battery dark mode Default tab - Select the tab that opens at launch + Choose which tab appears when the app opens Top stories and recent updates Android Studio tools, tips, and the full tutorial catalog App details, version, and credits @@ -362,8 +362,10 @@ Read our Privacy Policy Privacy Icon Arrow Icon - You\'re All Set - Thanks for setting up your preferences. Enjoy the app. + You\'re All Set! + Your setup is complete. You are now ready to explore all the features. + Get Started + Success Icon Allows the app to access and modify the device\'s notification policy, controlling how and when notifications are displayed to the user and providing custom notification management features. Allows the app to create and use services that run in the foreground, giving them priority over other background processes and improving performance and reliability. Set application language. From e9a128e0fec16650b1d288e980f5ac77f0a8ab41 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 18:48:49 +0300 Subject: [PATCH 08/10] Improve onboarding typography and theme selection --- .../ui/screens/onboarding/ThemeFragment.java | 21 +++++++++++++++---- .../main/res/drawable-anydpi/ic_dark_mode.xml | 3 +++ .../res/drawable-anydpi/ic_light_mode.xml | 3 +++ .../res/drawable-anydpi/ic_system_mode.xml | 3 +++ .../main/res/layout/activity_onboarding.xml | 2 +- .../layout/fragment_onboarding_selection.xml | 6 ++++-- 6 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable-anydpi/ic_dark_mode.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_light_mode.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_system_mode.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index fd790e24..218a4a0b 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -10,6 +10,7 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.appcompat.app.AppCompatDelegate; import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingSelectionBinding; @@ -26,13 +27,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.setTitle(getString(R.string.choose_your_style)); binding.setDescription(getString(R.string.select_how_you_d_like_the_app_to_look)); - binding.setFirstIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_theme)); + binding.setFirstIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_light_mode)); binding.setFirstTitle(getString(R.string.light_mode)); binding.setFirstDescription(getString(R.string.light_mode_description)); - binding.setSecondIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_about)); + binding.setSecondIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_dark_mode)); binding.setSecondTitle(getString(R.string.dark_mode)); binding.setSecondDescription(getString(R.string.dark_mode_description)); - binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_android)); + binding.setThirdIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_system_mode)); binding.setThirdTitle(getString(R.string.follow_system)); binding.setThirdDescription(getString(R.string.follow_system_mode_description)); @@ -63,15 +64,27 @@ private void selectOption(int index) { binding.optionFirst.radioButton.setChecked(index == 0); binding.optionSecond.radioButton.setChecked(index == 1); binding.optionThird.radioButton.setChecked(index == 2); + + int mode; + if (index == 0) { + mode = AppCompatDelegate.MODE_NIGHT_NO; + } else if (index == 1) { + mode = AppCompatDelegate.MODE_NIGHT_YES; + } else { + mode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + } + AppCompatDelegate.setDefaultNightMode(mode); } public void saveSelection() { String[] values = getResources().getStringArray(R.array.preference_theme_values); - String value = values[0]; + String value; if (binding.optionFirst.radioButton.isChecked()) { value = values[1]; } else if (binding.optionSecond.radioButton.isChecked()) { value = values[2]; + } else { + value = values[0]; } viewModel.setTheme(value); } diff --git a/app/src/main/res/drawable-anydpi/ic_dark_mode.xml b/app/src/main/res/drawable-anydpi/ic_dark_mode.xml new file mode 100644 index 00000000..7d7f49fe --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_dark_mode.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_light_mode.xml b/app/src/main/res/drawable-anydpi/ic_light_mode.xml new file mode 100644 index 00000000..a1948a01 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_light_mode.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_system_mode.xml b/app/src/main/res/drawable-anydpi/ic_system_mode.xml new file mode 100644 index 00000000..aeb2f984 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_system_mode.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index b431547c..62a9c1d7 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -24,7 +24,7 @@ app:layout_constraintBottom_toTopOf="@+id/bottomBar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/buttonSkip" /> From 8c9fda906a6acb5934c40bf65443d1f55b3bb08a Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Fri, 12 Sep 2025 19:12:10 +0300 Subject: [PATCH 09/10] Refine onboarding theme handling and integrate consent --- .../ui/screens/onboarding/DataFragment.java | 35 +++++++- .../ui/screens/onboarding/DoneFragment.java | 1 - .../onboarding/OnboardingActivity.java | 24 ++---- .../onboarding/OnboardingViewModel.java | 30 +++++++ .../ui/screens/onboarding/ThemeFragment.java | 31 +++++--- .../dialogs/ConsentDialogFragment.java | 79 ------------------- .../main/res/drawable-anydpi/ic_dark_mode.xml | 14 +++- .../res/drawable-anydpi/ic_light_mode.xml | 14 +++- .../res/drawable-anydpi/ic_system_mode.xml | 18 ++++- app/src/main/res/layout/dialog_consent.xml | 40 ---------- .../res/layout/fragment_onboarding_data.xml | 17 ++++ .../res/layout/fragment_onboarding_done.xml | 1 - app/src/main/res/values/strings.xml | 2 + 13 files changed, 147 insertions(+), 159 deletions(-) delete mode 100644 app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/dialogs/ConsentDialogFragment.java delete mode 100644 app/src/main/res/layout/dialog_consent.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java index ce23860b..31693cb8 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DataFragment.java @@ -13,6 +13,10 @@ import androidx.lifecycle.ViewModelProvider; import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingDataBinding; +import com.d4rk.androidtutorials.java.utils.ConsentUtils; +import androidx.preference.PreferenceManager; +import android.content.SharedPreferences; +import com.d4rk.androidtutorials.java.R; public class DataFragment extends Fragment { @@ -30,9 +34,27 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(OnboardingViewModel.class); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); + String keyAnalytics = getString(R.string.key_consent_analytics); + String keyAdPersonalization = getString(R.string.key_consent_ad_personalization); - binding.switchCrashlytics.setOnCheckedChangeListener((buttonView, isChecked) -> - viewModel.setCrashlyticsEnabled(isChecked)); + boolean analytics = prefs.getBoolean(keyAnalytics, true); + boolean ads = prefs.getBoolean(keyAdPersonalization, true); + binding.switchCrashlytics.setChecked(analytics); + binding.switchAds.setChecked(ads); + + binding.switchCrashlytics.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewModel.setCrashlyticsEnabled(isChecked); + viewModel.setConsentAnalytics(isChecked); + ConsentUtils.updateFirebaseConsent(requireContext(), isChecked, binding.switchAds.isChecked(), binding.switchAds.isChecked(), binding.switchAds.isChecked()); + }); + + binding.switchAds.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewModel.setConsentAdStorage(isChecked); + viewModel.setConsentAdUserData(isChecked); + viewModel.setConsentAdPersonalization(isChecked); + ConsentUtils.updateFirebaseConsent(requireContext(), binding.switchCrashlytics.isChecked(), isChecked, isChecked, isChecked); + }); binding.linkPrivacy.setOnClickListener(v -> { Intent intent = new Intent(Intent.ACTION_VIEW, @@ -42,7 +64,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } public void saveSelection() { - viewModel.setCrashlyticsEnabled(binding.switchCrashlytics.isChecked()); + boolean analytics = binding.switchCrashlytics.isChecked(); + boolean ads = binding.switchAds.isChecked(); + viewModel.setCrashlyticsEnabled(analytics); + viewModel.setConsentAnalytics(analytics); + viewModel.setConsentAdStorage(ads); + viewModel.setConsentAdUserData(ads); + viewModel.setConsentAdPersonalization(ads); + ConsentUtils.updateFirebaseConsent(requireContext(), analytics, ads, ads, ads); } @Override diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java index 69093a4f..cf0baf6b 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/DoneFragment.java @@ -8,7 +8,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import androidx.lifecycle.ViewModelProvider; import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingDoneBinding; diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java index 2bacf844..f44e6ca1 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java @@ -1,14 +1,12 @@ package com.d4rk.androidtutorials.java.ui.screens.onboarding; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.NonNull; -import androidx.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; @@ -17,8 +15,6 @@ import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.databinding.ActivityOnboardingBinding; import com.d4rk.androidtutorials.java.ui.screens.main.MainActivity; -import com.d4rk.androidtutorials.java.ui.screens.startup.dialogs.ConsentDialogFragment; -import com.d4rk.androidtutorials.java.utils.ConsentUtils; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; @@ -42,14 +38,9 @@ protected void onCreate(Bundle savedInstanceState) { adapter = new OnboardingPagerAdapter(this); binding.viewPager.setAdapter(adapter); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - String keyAnalytics = getString(R.string.key_consent_analytics); - if (!prefs.contains(keyAnalytics)) { - ConsentDialogFragment dialog = new ConsentDialogFragment(); - dialog.setConsentListener((a,b,c,d) -> ConsentUtils.updateFirebaseConsent(this, a,b,c,d)); - dialog.show(getSupportFragmentManager(), "consent"); - } + int startPage = viewModel.getCurrentPage(); + binding.viewPager.setCurrentItem(startPage, false); + currentPosition = startPage; binding.viewPager.registerOnPageChangeCallback(new androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback() { @Override @@ -66,6 +57,7 @@ public void onPageSelected(int position) { } } currentPosition = position; + viewModel.setCurrentPage(position); } }); @@ -80,9 +72,9 @@ public void onPageSelected(int position) { tab.setCustomView(dot); }).attach(); - TabLayout.Tab firstTab = binding.tabIndicator.getTabAt(0); - if (firstTab != null && firstTab.getCustomView() instanceof ImageView) { - ((ImageView) firstTab.getCustomView()).setImageResource(R.drawable.onboarding_dot_selected); + TabLayout.Tab startTab = binding.tabIndicator.getTabAt(startPage); + if (startTab != null && startTab.getCustomView() instanceof ImageView) { + ((ImageView) startTab.getCustomView()).setImageResource(R.drawable.onboarding_dot_selected); } binding.tabIndicator.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @@ -137,7 +129,7 @@ public void onTabReselected(TabLayout.Tab tab) { } }); - updateButtons(0); + updateButtons(startPage); } void finishOnboarding() { diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java index 6646253e..fd17d219 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingViewModel.java @@ -18,6 +18,7 @@ public class OnboardingViewModel extends ViewModel { private final Context context; private final SharedPreferences prefs; + private int currentPage = 0; @Inject public OnboardingViewModel(@ApplicationContext Context context) { @@ -29,6 +30,19 @@ public void setTheme(String value) { prefs.edit().putString(context.getString(R.string.key_theme), value).apply(); } + public String getTheme() { + String[] values = context.getResources().getStringArray(R.array.preference_theme_values); + return prefs.getString(context.getString(R.string.key_theme), values[0]); + } + + public void setCurrentPage(int page) { + currentPage = page; + } + + public int getCurrentPage() { + return currentPage; + } + public void setDefaultTab(String value) { prefs.edit().putString(context.getString(R.string.key_default_tab), value).apply(); } @@ -45,6 +59,22 @@ public void setCrashlyticsEnabled(boolean enabled) { prefs.edit().putBoolean(context.getString(R.string.key_firebase_crashlytics), enabled).apply(); } + public void setConsentAnalytics(boolean enabled) { + prefs.edit().putBoolean(context.getString(R.string.key_consent_analytics), enabled).apply(); + } + + public void setConsentAdStorage(boolean enabled) { + prefs.edit().putBoolean(context.getString(R.string.key_consent_ad_storage), enabled).apply(); + } + + public void setConsentAdUserData(boolean enabled) { + prefs.edit().putBoolean(context.getString(R.string.key_consent_ad_user_data), enabled).apply(); + } + + public void setConsentAdPersonalization(boolean enabled) { + prefs.edit().putBoolean(context.getString(R.string.key_consent_ad_personalization), enabled).apply(); + } + public void markOnboardingComplete() { prefs.edit().putBoolean(context.getString(R.string.key_onboarding_complete), true).apply(); } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java index 218a4a0b..ebe32541 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/ThemeFragment.java @@ -7,10 +7,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.appcompat.app.AppCompatDelegate; import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.databinding.FragmentOnboardingSelectionBinding; @@ -49,7 +49,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.optionSecond.radioButton.setId(View.generateViewId()); binding.optionThird.radioButton.setId(View.generateViewId()); - selectOption(2); + String themeValue = viewModel.getTheme(); + String[] values = getResources().getStringArray(R.array.preference_theme_values); + int index = 2; // default follow system + if (themeValue.equals(values[1])) index = 0; + else if (themeValue.equals(values[2])) index = 1; + setRadioButtons(index); binding.cardFirst.setOnClickListener(v -> selectOption(0)); binding.cardSecond.setOnClickListener(v -> selectOption(1)); @@ -60,33 +65,33 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.optionThird.radioButton.setOnClickListener(v -> selectOption(2)); } - private void selectOption(int index) { + private void setRadioButtons(int index) { binding.optionFirst.radioButton.setChecked(index == 0); binding.optionSecond.radioButton.setChecked(index == 1); binding.optionThird.radioButton.setChecked(index == 2); + } + private void selectOption(int index) { + setRadioButtons(index); int mode; + String[] values = getResources().getStringArray(R.array.preference_theme_values); + String value; if (index == 0) { mode = AppCompatDelegate.MODE_NIGHT_NO; + value = values[1]; } else if (index == 1) { mode = AppCompatDelegate.MODE_NIGHT_YES; + value = values[2]; } else { mode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + value = values[0]; } + viewModel.setTheme(value); AppCompatDelegate.setDefaultNightMode(mode); } public void saveSelection() { - String[] values = getResources().getStringArray(R.array.preference_theme_values); - String value; - if (binding.optionFirst.radioButton.isChecked()) { - value = values[1]; - } else if (binding.optionSecond.radioButton.isChecked()) { - value = values[2]; - } else { - value = values[0]; - } - viewModel.setTheme(value); + // theme stored on selection } @Override diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/dialogs/ConsentDialogFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/dialogs/ConsentDialogFragment.java deleted file mode 100644 index 00ab6da6..00000000 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/startup/dialogs/ConsentDialogFragment.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.d4rk.androidtutorials.java.ui.screens.startup.dialogs; - -import android.app.Dialog; -import android.content.SharedPreferences; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.preference.PreferenceManager; - -import com.d4rk.androidtutorials.java.BuildConfig; -import com.d4rk.androidtutorials.java.R; -import com.d4rk.androidtutorials.java.databinding.DialogConsentBinding; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -public class ConsentDialogFragment extends DialogFragment { - - private ConsentListener listener; - - public void setConsentListener(ConsentListener listener) { - this.listener = listener; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - DialogConsentBinding binding = DialogConsentBinding.inflate(getLayoutInflater()); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); - boolean defaultChecked = !BuildConfig.DEBUG; - - // Cache preference keys early so the dialog can still operate safely if the - // fragment gets detached before the positive button callback runs. - String keyAnalytics = getString(R.string.key_consent_analytics); - String keyAdStorage = getString(R.string.key_consent_ad_storage); - String keyAdUserData = getString(R.string.key_consent_ad_user_data); - String keyAdPersonalization = getString(R.string.key_consent_ad_personalization); - - boolean analytics = prefs.getBoolean(keyAnalytics, defaultChecked); - boolean adStorage = prefs.getBoolean(keyAdStorage, defaultChecked); - boolean adUserData = prefs.getBoolean(keyAdUserData, defaultChecked); - boolean adPersonalization = prefs.getBoolean(keyAdPersonalization, defaultChecked); - - binding.checkAnalyticsStorage.setChecked(analytics); - binding.checkAdStorage.setChecked(adStorage); - binding.checkAdUserData.setChecked(adUserData); - binding.checkAdPersonalization.setChecked(adPersonalization); - - setCancelable(false); - - return new MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.consent_dialog_title) - .setView(binding.getRoot()) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - boolean a = binding.checkAnalyticsStorage.isChecked(); - boolean b = binding.checkAdStorage.isChecked(); - boolean c = binding.checkAdUserData.isChecked(); - boolean d = binding.checkAdPersonalization.isChecked(); - - prefs.edit() - .putBoolean(keyAnalytics, a) - .putBoolean(keyAdStorage, b) - .putBoolean(keyAdUserData, c) - .putBoolean(keyAdPersonalization, d) - .apply(); - - if (listener != null) { - listener.onConsentSet(a, b, c, d); - } - }) - .create(); - } - - public interface ConsentListener { - void onConsentSet(boolean analytics, boolean adStorage, boolean adUserData, boolean adPersonalization); - } -} diff --git a/app/src/main/res/drawable-anydpi/ic_dark_mode.xml b/app/src/main/res/drawable-anydpi/ic_dark_mode.xml index 7d7f49fe..7063d45d 100644 --- a/app/src/main/res/drawable-anydpi/ic_dark_mode.xml +++ b/app/src/main/res/drawable-anydpi/ic_dark_mode.xml @@ -1,3 +1,13 @@ - - + + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_light_mode.xml b/app/src/main/res/drawable-anydpi/ic_light_mode.xml index a1948a01..281ec137 100644 --- a/app/src/main/res/drawable-anydpi/ic_light_mode.xml +++ b/app/src/main/res/drawable-anydpi/ic_light_mode.xml @@ -1,3 +1,13 @@ - - + + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_system_mode.xml b/app/src/main/res/drawable-anydpi/ic_system_mode.xml index aeb2f984..eff884fc 100644 --- a/app/src/main/res/drawable-anydpi/ic_system_mode.xml +++ b/app/src/main/res/drawable-anydpi/ic_system_mode.xml @@ -1,3 +1,17 @@ - - + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_consent.xml b/app/src/main/res/layout/dialog_consent.xml deleted file mode 100644 index 61a15a82..00000000 --- a/app/src/main/res/layout/dialog_consent.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_data.xml b/app/src/main/res/layout/fragment_onboarding_data.xml index e03bd8b1..ff1fe603 100644 --- a/app/src/main/res/layout/fragment_onboarding_data.xml +++ b/app/src/main/res/layout/fragment_onboarding_data.xml @@ -65,6 +65,23 @@ android:layout_marginTop="8dp" android:text="@string/onboarding_data_active" android:textColor="?attr/colorOnSurfaceVariant" /> + + + + diff --git a/app/src/main/res/layout/fragment_onboarding_done.xml b/app/src/main/res/layout/fragment_onboarding_done.xml index 02fc1672..9f83de35 100644 --- a/app/src/main/res/layout/fragment_onboarding_done.xml +++ b/app/src/main/res/layout/fragment_onboarding_done.xml @@ -1,7 +1,6 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c264467c..f99de7fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -359,6 +359,8 @@ Help us make the app better for you by sharing anonymous crash reports and usage statistics. Enable Crash Reporting By enabling this, you help us identify and fix bugs faster. We do not collect any personal information. + Allow Personalized Ads + Permit storing ad data, user info, and personalization to keep ads relevant. Read our Privacy Policy Privacy Icon Arrow Icon From 13095838f13fe005ed9369634d2d630aa9a58d33 Mon Sep 17 00:00:00 2001 From: D4rK7355608 Date: Fri, 12 Sep 2025 22:56:41 +0300 Subject: [PATCH 10/10] Refactor and update UI elements This commit introduces several changes: - Refactored `MainUiState` and test `FakeHomeRepository` & `FakeQuizLocalDataSource` to use Java records. - Updated various icons (`ic_arrow_back`, `ic_arrow_forward`, `ic_close`, `ic_security`) with new path data and added `ic_open_new`. - Modified layout files for onboarding (`fragment_onboarding_data.xml`, `fragment_onboarding_done.xml`, `activity_onboarding.xml`) to adjust icon gravity and replace a LinearLayout with a MaterialButton. - Updated text strings and removed unused ones in `strings.xml`. - Renamed `getDailyTip()` to `dailyTip()` in `HomeRepository` and its implementations. - Added FIXME comments for potential null pointer exceptions, deprecated methods, and unused code in several Java files. - Changed GitHub username in `OpenSourceLicensesUtils.java`. - Updated card view shape appearance in `fragment_onboarding_selection.xml`. - Simplified fragment creation in `OnboardingActivity` using an enhanced switch. --- .../java/ads/views/NativeAdBannerView.java | 4 +- .../repository/DefaultHomeRepository.java | 2 +- .../java/data/repository/HomeRepository.java | 2 +- .../java/domain/home/GetDailyTipUseCase.java | 2 +- .../lessons/data/room/RoomActivity.java | 2 +- .../networking/retrofit/RetrofitActivity.java | 6 +-- .../start/AndroidStartProjectActivity.java | 2 +- .../passwordbox/PasswordBoxActivity.java | 2 +- .../java/ui/screens/home/HomeFragment.java | 2 +- .../java/ui/screens/main/MainActivity.java | 27 +++++++------ .../java/ui/screens/main/MainUiState.java | 26 +----------- .../onboarding/OnboardingActivity.java | 16 +++----- .../java/ui/screens/quiz/QuizActivity.java | 4 +- .../java/ui/screens/quiz/QuizViewModel.java | 12 +++--- .../repository/SettingsRepository.java | 2 +- .../java/utils/FontManager.java | 4 +- .../java/utils/OpenSourceLicensesUtils.java | 4 +- .../main/res/drawable-anydpi/ic_open_new.xml | 13 ++++++ app/src/main/res/drawable/ic_arrow_back.xml | 6 +-- .../main/res/drawable/ic_arrow_forward.xml | 10 +++-- app/src/main/res/drawable/ic_close.xml | 9 +++-- app/src/main/res/drawable/ic_security.xml | 8 ++-- .../main/res/layout/activity_onboarding.xml | 2 +- .../res/layout/fragment_onboarding_data.xml | 33 +++++---------- .../res/layout/fragment_onboarding_done.xml | 2 +- .../layout/fragment_onboarding_selection.xml | 7 ++-- app/src/main/res/values/strings.xml | 11 ----- .../repository/DefaultHomeRepositoryTest.java | 2 +- .../repository/DefaultQuizRepositoryTest.java | 14 +++---- .../ui/screens/home/HomeViewModelTest.java | 40 +++++++------------ 30 files changed, 116 insertions(+), 160 deletions(-) create mode 100644 app/src/main/res/drawable-anydpi/ic_open_new.xml diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ads/views/NativeAdBannerView.java b/app/src/main/java/com/d4rk/androidtutorials/java/ads/views/NativeAdBannerView.java index eec659dc..b1fb9a0d 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ads/views/NativeAdBannerView.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ads/views/NativeAdBannerView.java @@ -39,7 +39,7 @@ public NativeAdBannerView(@NonNull Context context, @Nullable AttributeSet attrs private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { if (attrs != null) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NativeAdBannerView, defStyleAttr, 0); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NativeAdBannerView, defStyleAttr, 0); // FIXME: 'TypedArray' used without 'try'-with-resources statement layoutRes = a.getResourceId(R.styleable.NativeAdBannerView_nativeAdLayout, R.layout.ad_home_banner_large); a.recycle(); } @@ -49,7 +49,7 @@ public void loadAd(AdRequest adRequest) { loadAd(adRequest, null); } - public void loadAd(AdRequest adRequest, @Nullable AdListener listener) { + public void loadAd(AdRequest adRequest, @Nullable AdListener listener) { // FIXME: Parameter 'adRequest' is never used NativeAdLoader.load(getContext(), this, layoutRes, listener); } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepository.java index f2714fe5..c1ebd017 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepository.java @@ -28,7 +28,7 @@ public String getAppPlayStoreUrl(String packageName) { } @Override - public String getDailyTip() { + public String dailyTip() { return localDataSource.getDailyTip(); } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/HomeRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/HomeRepository.java index 26254989..63a30037 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/HomeRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/data/repository/HomeRepository.java @@ -13,7 +13,7 @@ public interface HomeRepository { String getAppPlayStoreUrl(String packageName); - String getDailyTip(); + String dailyTip(); void fetchPromotedApps(PromotedAppsCallback callback); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetDailyTipUseCase.java b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetDailyTipUseCase.java index 7d9e6141..3f05c74a 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetDailyTipUseCase.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetDailyTipUseCase.java @@ -16,6 +16,6 @@ public GetDailyTipUseCase(HomeRepository repository) { * Returns today's tip string. */ public String invoke() { - return repository.getDailyTip(); + return repository.dailyTip(); } } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/data/room/RoomActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/data/room/RoomActivity.java index 3c962495..78b39541 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/data/room/RoomActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/data/room/RoomActivity.java @@ -89,7 +89,7 @@ protected void onDestroy() { private static class NotesAdapter extends ListAdapter { private static final DiffUtil.ItemCallback DIFF_CALLBACK = - new DiffUtil.ItemCallback() { + new DiffUtil.ItemCallback<>() { @Override public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { return oldItem.id == newItem.id; diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/networking/retrofit/RetrofitActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/networking/retrofit/RetrofitActivity.java index 3ba23381..3951c1f9 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/networking/retrofit/RetrofitActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/networking/retrofit/RetrofitActivity.java @@ -44,9 +44,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { binding.buttonFetch.setOnClickListener(v -> { binding.buttonFetch.setEnabled(false); - api.getTodo().enqueue(new Callback() { + api.getTodo().enqueue(new Callback<>() { @Override - public void onResponse(Call call, Response response) { + public void onResponse(Call call, Response response) { // FIXME: Not annotated parameter overrides @EverythingIsNonNull parameter if (response.isSuccessful() && response.body() != null) { binding.textViewResult.setText(response.body().title); } else { @@ -56,7 +56,7 @@ public void onResponse(Call call, Response response) { } @Override - public void onFailure(Call call, Throwable t) { + public void onFailure(Call call, Throwable t) { // FIXME: Not annotated parameter overrides @EverythingIsNonNull parameter binding.textViewResult.setText(R.string.snack_general_error); binding.buttonFetch.setEnabled(true); } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/start/AndroidStartProjectActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/start/AndroidStartProjectActivity.java index 08cd83ab..4a6f974f 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/start/AndroidStartProjectActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/start/AndroidStartProjectActivity.java @@ -25,7 +25,7 @@ protected void onCreate(Bundle savedInstanceState) { edgeToEdgeDelegate.applyEdgeToEdge(binding.constraintLayout); setSupportActionBar(binding.topAppBar); - binding.topAppBar.setNavigationOnClickListener(v -> onBackPressed()); + binding.topAppBar.setNavigationOnClickListener(v -> onBackPressed()); // FIXME: 'onBackPressed()' is deprecated binding.topAppBar.setOnMenuItemClickListener(item -> { if (item.getItemId() == R.id.action_share) { Intent sharingIntent = new Intent(Intent.ACTION_SEND); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/textboxes/passwordbox/PasswordBoxActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/textboxes/passwordbox/PasswordBoxActivity.java index 75844a8f..75908d6e 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/textboxes/passwordbox/PasswordBoxActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/textboxes/passwordbox/PasswordBoxActivity.java @@ -62,7 +62,7 @@ private void hidePassword() { private void addKeyListener() { binding.buttonShowPassword.setOnClickListener(v -> - Snackbar.make(binding.getRoot(), binding.editText.getText(), Snackbar.LENGTH_LONG).show()); + Snackbar.make(binding.getRoot(), binding.editText.getText(), Snackbar.LENGTH_LONG).show()); // FIXME: Argument 'binding.editText.getText()' might be null } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java index af22fb7f..228abb19 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java @@ -107,7 +107,7 @@ private void shareTip(String tip) { private void shareApp(com.d4rk.androidtutorials.java.data.model.PromotedApp app) { android.content.Intent sharingIntent = new android.content.Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); - String shareLink = homeViewModel.getPromotedAppIntent(app.packageName()).getData().toString(); + String shareLink = homeViewModel.getPromotedAppIntent(app.packageName()).getData().toString(); // FIXME: Method invocation 'toString' may produce 'NullPointerException' String shareMessage = getString(com.d4rk.androidtutorials.java.R.string.share_message, shareLink); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareMessage); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(com.d4rk.androidtutorials.java.R.string.share_subject)); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java index 704f77fd..f29ccf56 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java @@ -76,13 +76,13 @@ public void onResume(@NonNull LifecycleOwner owner) { if (ConsentUtils.canShowAds(MainActivity.this)) { if (mBinding.adView.getVisibility() != View.VISIBLE) { MobileAds.initialize(MainActivity.this); - mBinding.adPlaceholder.setVisibility(View.GONE); + mBinding.adPlaceholder.setVisibility(View.GONE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' mBinding.adView.setVisibility(View.VISIBLE); mBinding.adView.loadAd(new AdRequest.Builder().build()); } } else { mBinding.adView.setVisibility(View.GONE); - mBinding.adPlaceholder.setVisibility(View.VISIBLE); + mBinding.adPlaceholder.setVisibility(View.VISIBLE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' } } } @@ -90,7 +90,7 @@ public void onResume(@NonNull LifecycleOwner owner) { private MainViewModel mainViewModel; private NavController navController; private int currentNavIndex; - private AppUpdateNotificationsManager appUpdateNotificationsManager; + private AppUpdateNotificationsManager appUpdateNotificationsManager; // FIXME: Private field 'appUpdateNotificationsManager' is assigned but never accessed private AppUpdateManager appUpdateManager; private InstallStateUpdatedListener installStateUpdatedListener; private long backPressedTime; @@ -173,24 +173,24 @@ private void observeViewModel() { EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this); NavigationBarView navBarView = (NavigationBarView) mBinding.navView; if (useRail) { - mBinding.navRail.setVisibility(View.VISIBLE); + mBinding.navRail.setVisibility(View.VISIBLE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' navBarView.setVisibility(View.GONE); edgeToEdgeDelegate.applyEdgeToEdge(mBinding.container); } else { - mBinding.navRail.setVisibility(View.GONE); + mBinding.navRail.setVisibility(View.GONE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' navBarView.setVisibility(View.VISIBLE); edgeToEdgeDelegate.applyEdgeToEdgeBottomBar(mBinding.container, navBarView); - navBarView.setLabelVisibilityMode(uiState.getBottomNavVisibility()); + navBarView.setLabelVisibilityMode(uiState.bottomNavVisibility()); if (mBinding.adView != null) { if (ConsentUtils.canShowAds(this)) { MobileAds.initialize(this); - mBinding.adPlaceholder.setVisibility(View.GONE); + mBinding.adPlaceholder.setVisibility(View.GONE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' mBinding.adView.setVisibility(View.VISIBLE); mBinding.adView.loadAd(new AdRequest.Builder().build()); } else { mBinding.adView.setVisibility(View.GONE); - mBinding.adPlaceholder.setVisibility(View.VISIBLE); + mBinding.adPlaceholder.setVisibility(View.VISIBLE); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' } } } @@ -200,13 +200,13 @@ private void observeViewModel() { if (navHostFragment != null) { navController = navHostFragment.getNavController(); NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation); - navGraph.setStartDestination(uiState.getDefaultNavDestination()); + navGraph.setStartDestination(uiState.defaultNavDestination()); navController.setGraph(navGraph); navOrder.put(R.id.navigation_home, 0); navOrder.put(R.id.navigation_android_studio, 1); navOrder.put(R.id.navigation_about, 2); - currentNavIndex = navOrder.get(navController.getCurrentDestination().getId()); + currentNavIndex = navOrder.get(navController.getCurrentDestination().getId()); // FIXME: Method invocation 'getId' may produce 'NullPointerException' NavOptions forwardOptions = new NavOptions.Builder() .setEnterAnim(R.anim.fragment_spring_enter) @@ -256,13 +256,13 @@ private void observeViewModel() { }); } - if (uiState.isThemeChanged()) { + if (uiState.themeChanged()) { recreate(); } }); mainViewModel.getLoadingState().observe(this, isLoading -> - mBinding.progressBar.setVisibility(Boolean.TRUE.equals(isLoading) ? View.VISIBLE : View.GONE)); + mBinding.progressBar.setVisibility(Boolean.TRUE.equals(isLoading) ? View.VISIBLE : View.GONE)); // FIXME: Method invocation 'setVisibility' may produce 'NullPointerException' } private void setupUpdateNotifications() { @@ -285,7 +285,8 @@ public boolean onOptionsItemSelected(android.view.MenuItem item) { return super.onOptionsItemSelected(item); } - private void checkForImmediateUpdate() { + // TODO: Call on onResume + private void checkForImmediateUpdate() { // FIXME: Private method 'checkForImmediateUpdate()' is never used appUpdateManager .getAppUpdateInfo() .addOnSuccessListener( diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainUiState.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainUiState.java index cf9d9074..0b4a6b2b 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainUiState.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainUiState.java @@ -7,28 +7,6 @@ * bottom navigation visibility, the default navigation destination, and whether the theme * has changed requiring a recreation of the activity. */ -public class MainUiState { - @NavigationBarView.LabelVisibility - private final int bottomNavVisibility; - private final int defaultNavDestination; - private final boolean themeChanged; - - public MainUiState(@NavigationBarView.LabelVisibility int bottomNavVisibility, int defaultNavDestination, boolean themeChanged) { - this.bottomNavVisibility = bottomNavVisibility; - this.defaultNavDestination = defaultNavDestination; - this.themeChanged = themeChanged; - } - - @NavigationBarView.LabelVisibility - public int getBottomNavVisibility() { - return bottomNavVisibility; - } - - public int getDefaultNavDestination() { - return defaultNavDestination; - } - - public boolean isThemeChanged() { - return themeChanged; - } +public record MainUiState(@NavigationBarView.LabelVisibility int bottomNavVisibility, + int defaultNavDestination, boolean themeChanged) { } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java index f44e6ca1..b6dfaba6 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/onboarding/OnboardingActivity.java @@ -159,16 +159,12 @@ private static class OnboardingPagerAdapter extends FragmentStateAdapter { @NonNull @Override public Fragment createFragment(int position) { - switch (position) { - case 0: - return new ThemeFragment(); - case 1: - return new StartPageFragment(); - case 2: - return new DataFragment(); - default: - return new DoneFragment(); - } + return switch (position) { + case 0 -> new ThemeFragment(); + case 1 -> new StartPageFragment(); + case 2 -> new DataFragment(); + default -> new DoneFragment(); + }; } @Override diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java index 86319e98..1bc070a2 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java @@ -69,7 +69,7 @@ private void onNextClicked() { if (selectedIndex != -1) { viewModel.answer(selectedIndex); } - if (viewModel.getCurrentIndex().getValue() >= viewModel.getTotalQuestions()) { + if (viewModel.getCurrentIndex().getValue() >= viewModel.getTotalQuestions()) { // FIXME: Unboxing of 'viewModel.getCurrentIndex().getValue()' may produce 'NullPointerException' showResult(); } else { showQuestion(viewModel.getCurrentQuestion()); @@ -89,7 +89,7 @@ private void showQuestion(QuizQuestion question) { } private void showResult() { - int score = viewModel.getScore().getValue(); + int score = viewModel.getScore().getValue(); // FIXME: Unboxing of 'viewModel.getScore().getValue()' may produce 'NullPointerException' int total = viewModel.getTotalQuestions(); View view = LayoutInflater.from(this).inflate(R.layout.dialog_quiz_result, null, false); TextView textResult = view.findViewById(R.id.text_result); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizViewModel.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizViewModel.java index 6365b501..99ab4a34 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizViewModel.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizViewModel.java @@ -23,18 +23,18 @@ public class QuizViewModel extends ViewModel { private final MutableLiveData> questions = new MutableLiveData<>(Collections.emptyList()); private final MutableLiveData currentIndex = new MutableLiveData<>(0); private final MutableLiveData score = new MutableLiveData<>(0); - private final LoadQuizQuestionsUseCase loadQuizQuestionsUseCase; + private final LoadQuizQuestionsUseCase loadQuizQuestionsUseCase; // FIXME: Field can be converted to a local variable && Private field 'loadQuizQuestionsUseCase' is assigned but never accessed @Inject public QuizViewModel(LoadQuizQuestionsUseCase loadQuizQuestionsUseCase) { this.loadQuizQuestionsUseCase = loadQuizQuestionsUseCase; - loadQuizQuestionsUseCase.invoke(result -> questions.postValue(result)); + loadQuizQuestionsUseCase.invoke(questions::postValue); } public QuizQuestion getCurrentQuestion() { List list = questions.getValue(); if (list == null || list.isEmpty()) return null; - int index = currentIndex.getValue(); + int index = currentIndex.getValue(); // FIXME: Unboxing of 'currentIndex.getValue()' may produce 'NullPointerException' return list.get(Math.min(index, list.size() - 1)); } @@ -46,16 +46,16 @@ public LiveData getScore() { return score; } - public LiveData> getQuestions() { + public LiveData> getQuestions() { // FIXME: Method 'getQuestions()' is never used return questions; } public void answer(int optionIndex) { QuizQuestion question = getCurrentQuestion(); if (question != null && optionIndex == question.answerIndex()) { - score.setValue(score.getValue() + 1); + score.setValue(score.getValue() + 1); // FIXME: Unboxing of 'score.getValue()' may produce 'NullPointerException' } - currentIndex.setValue(currentIndex.getValue() + 1); + currentIndex.setValue(currentIndex.getValue() + 1); // FIXME: Unboxing of 'currentIndex.getValue()' may produce 'NullPointerException' } public int getTotalQuestions() { diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/repository/SettingsRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/repository/SettingsRepository.java index 975cb375..00e059dc 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/repository/SettingsRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/repository/SettingsRepository.java @@ -51,7 +51,7 @@ public boolean applyTheme() { String preference = sharedPreferences.getString(preferenceKey, defaultThemeValue); int currentNightMode = AppCompatDelegate.getDefaultNightMode(); - int newNightMode = getNewNightMode(currentNightMode, preference, darkModeValues); + int newNightMode = getNewNightMode(currentNightMode, preference, darkModeValues); // FIXME: Argument 'preference' might be null if (newNightMode != currentNightMode) { AppCompatDelegate.setDefaultNightMode(newNightMode); return true; diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/utils/FontManager.java b/app/src/main/java/com/d4rk/androidtutorials/java/utils/FontManager.java index 4038e0db..91f395f5 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/utils/FontManager.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/utils/FontManager.java @@ -19,14 +19,14 @@ public static Typeface getMonospaceFont(Context context, SharedPreferences prefs prefs.edit().remove(key).apply(); font = "6"; } - return switch (font) { + return switch (font) { // FIXME: Dereference of 'font' may produce 'NullPointerException' case "0" -> ResourcesCompat.getFont(context, R.font.font_audiowide); case "1" -> ResourcesCompat.getFont(context, R.font.font_fira_code); case "2" -> ResourcesCompat.getFont(context, R.font.font_jetbrains_mono); case "3" -> ResourcesCompat.getFont(context, R.font.font_noto_sans_mono); case "4" -> ResourcesCompat.getFont(context, R.font.font_poppins); case "5" -> ResourcesCompat.getFont(context, R.font.font_roboto_mono); - case "6" -> ResourcesCompat.getFont(context, R.font.font_google_sans_code); + case "6" -> ResourcesCompat.getFont(context, R.font.font_google_sans_code); // FIXME: Branch in 'switch' is a duplicate of the default branch default -> ResourcesCompat.getFont(context, R.font.font_google_sans_code); }; } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java b/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java index c4e23ebb..3dc9d3c1 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java @@ -30,8 +30,8 @@ public static void loadHtmlData(final Context context, final HtmlDataCallback ca executor.execute(() -> { String packageName = context.getPackageName(); String currentVersion = getAppVersion(context); - String changelogUrl = "https://raw.githubusercontent.com/D4rK7355608/" + packageName + "/refs/heads/main/CHANGELOG.md"; - String eulaUrl = "https://raw.githubusercontent.com/D4rK7355608/" + packageName + "/refs/heads/main/EULA.md"; + String changelogUrl = "https://raw.githubusercontent.com/MihaiCristianCondrea/" + packageName + "/refs/heads/main/CHANGELOG.md"; + String eulaUrl = "https://raw.githubusercontent.com/MihaiCristianCondrea/" + packageName + "/refs/heads/main/EULA.md"; String changelogMarkdown = loadMarkdown(context, changelogUrl, R.string.error_loading_changelog); String extractedChangelog = extractLatestVersionChangelog(changelogMarkdown, currentVersion); diff --git a/app/src/main/res/drawable-anydpi/ic_open_new.xml b/app/src/main/res/drawable-anydpi/ic_open_new.xml new file mode 100644 index 00000000..63a3c643 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_open_new.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml index 261ff5f3..5498475b 100644 --- a/app/src/main/res/drawable/ic_arrow_back.xml +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -5,6 +5,6 @@ android:viewportWidth="24" android:viewportHeight="24"> - + android:fillColor="#000000" + android:pathData="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" /> + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml index c4688d61..0a68f5fe 100644 --- a/app/src/main/res/drawable/ic_arrow_forward.xml +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -1,9 +1,13 @@ + + - + android:pathData="M0 0h24v24H0V0z" /> + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index 29fee3b1..3099c0a9 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -4,7 +4,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> + - + android:pathData="M0 0h24v24H0V0z" /> + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml index 332ad012..7cb8b532 100644 --- a/app/src/main/res/drawable/ic_security.xml +++ b/app/src/main/res/drawable/ic_security.xml @@ -1,9 +1,11 @@ + + - + android:fillColor="#000000" + android:pathData="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-0.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index 62a9c1d7..0a396bb6 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -64,7 +64,7 @@ android:layout_height="wrap_content" android:text="@string/next" app:icon="@drawable/ic_arrow_forward" - app:iconGravity="textStart" /> + app:iconGravity="textEnd" /> diff --git a/app/src/main/res/layout/fragment_onboarding_data.xml b/app/src/main/res/layout/fragment_onboarding_data.xml index ff1fe603..d2a96c39 100644 --- a/app/src/main/res/layout/fragment_onboarding_data.xml +++ b/app/src/main/res/layout/fragment_onboarding_data.xml @@ -85,31 +85,16 @@ - - - - - - + android:layout_marginTop="16dp" + android:text="@string/privacy_policy_link" + app:icon="@drawable/ic_open_new" + app:iconGravity="end" /> diff --git a/app/src/main/res/layout/fragment_onboarding_done.xml b/app/src/main/res/layout/fragment_onboarding_done.xml index 9f83de35..c6b83733 100644 --- a/app/src/main/res/layout/fragment_onboarding_done.xml +++ b/app/src/main/res/layout/fragment_onboarding_done.xml @@ -68,7 +68,7 @@ android:layout_marginTop="24dp" android:text="@string/get_started_button" app:icon="@drawable/ic_arrow_forward" - app:iconGravity="textStart" /> + app:iconGravity="textEnd" /> diff --git a/app/src/main/res/layout/fragment_onboarding_selection.xml b/app/src/main/res/layout/fragment_onboarding_selection.xml index 4addbfe7..ff0f2530 100644 --- a/app/src/main/res/layout/fragment_onboarding_selection.xml +++ b/app/src/main/res/layout/fragment_onboarding_selection.xml @@ -60,8 +60,8 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="@{title}" - android:textAppearance="?attr/textAppearanceHeadlineMedium" android:textAlignment="center" + android:textAppearance="?attr/textAppearanceHeadlineMedium" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -72,9 +72,9 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@{description}" + android:textAlignment="center" android:textAppearance="?attr/textAppearanceBodyMedium" android:textColor="?attr/colorOnSurfaceVariant" - android:textAlignment="center" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> @@ -100,7 +100,8 @@ + android:layout_height="wrap_content" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.CardViewTopRounded"> Learn more Play Store Ad - Search tutorials - Search tutorials Search lessons Search lessons @@ -316,7 +314,6 @@ Version %1$s (%2$d) Music Made with ❤ in Romania. - Close? Restart required. What is Android Studio Tutorials: Java Edition? How can I download Android Studio Tutorials: Java Edition? @@ -343,18 +340,14 @@ Allows the app to use the Google Play Billing Library to handle in-app purchases and donations. Allows the app to verify its compliance with the license agreement and enforce licensing terms to protect intellectual property. Next - Finish Back Bottom navigation labels - We collect data to improve your experience. Skip Choose your style Choose how the app looks Bright, clean appearance Comfortable in low light Follows your device theme - Use the system theme or choose a custom one - Switches to dark at night or when Battery Saver is on Help Us Improve Your Experience Help us make the app better for you by sharing anonymous crash reports and usage statistics. Enable Crash Reporting @@ -363,7 +356,6 @@ Permit storing ad data, user info, and personalization to keep ads relevant. Read our Privacy Policy Privacy Icon - Arrow Icon You\'re All Set! Your setup is complete. You are now ready to explore all the features. Get Started @@ -411,7 +403,6 @@ Learn how to use inbox style notifications in your Android app with this lesson. Discover how to create a notification channel and builder, and how to set the style of your notifications to an InboxStyle with multiple lines of text and a summary text. Explore the different options available for customizing your inbox style notifications. A bottom navigation bar lets you quickly switch between top-level views in your app. A navigation drawer slides in from the side and displays the app\'s main navigation options. - Are you sure you want to exit? This will be the message you will see on screen. To take effect, please restart the app. The Android Software Development Kit (SDK) is a collection of tools that allow developers to create Android apps. It includes a set of libraries, a debugger, a handset emulator, and documentation. The SDK also includes an API library and a set of API documentation. The packages you download have libraries, which helps you in creating your app.\n\nThis is an overview of all Android versions and their corresponding identifiers for Android developers. Anyone is welcome to open an issue or pull request. Happy developing. @@ -436,7 +427,6 @@ Thanks for your %1$.1f-star rating. ❤️ Image button clicked. This is a toast. - Show code syntax Show Java code snippet Open me 🌐 Type here @@ -455,7 +445,6 @@ Error loading layout Error loading code An error occurred while checking for updates - Data and ads consent Analytics storage Ad storage Ad user data diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java index e9c0fde4..0205f46c 100644 --- a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java +++ b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java @@ -24,7 +24,7 @@ public void repositoryDelegatesToDataSources() { assertEquals("play", repository.getPlayStoreUrl()); assertEquals("play/pkg", repository.getAppPlayStoreUrl("pkg")); - assertEquals("tip", repository.getDailyTip()); + assertEquals("tip", repository.dailyTip()); AtomicReference> result = new AtomicReference<>(); repository.fetchPromotedApps(result::set); diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java index f8923820..8d258ad8 100644 --- a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java +++ b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java @@ -29,16 +29,12 @@ public void loadQuestionsReturnsLocalData() throws InterruptedException { assertTrue(latch.await(1, TimeUnit.SECONDS)); } - private static class FakeQuizLocalDataSource implements QuizLocalDataSource { - private final List questions; - - FakeQuizLocalDataSource(List questions) { - this.questions = questions; - } + private record FakeQuizLocalDataSource( + List questions) implements QuizLocalDataSource { @Override - public void loadQuestions(QuestionsCallback callback) { - callback.onResult(questions); + public void loadQuestions(QuestionsCallback callback) { + callback.onResult(questions); + } } - } } diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java index 50f3026b..b59b860c 100644 --- a/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java +++ b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java @@ -60,33 +60,21 @@ public void uiStateHandlesEmptyPromotedApps() { assertTrue(state.promotedApps().isEmpty()); } - static class FakeHomeRepository implements HomeRepository { - final String dailyTip; - final List apps; - - FakeHomeRepository(String dailyTip, List apps) { - this.dailyTip = dailyTip; - this.apps = apps; - } - - @Override - public String getPlayStoreUrl() { - return ""; - } - - @Override - public String getAppPlayStoreUrl(String packageName) { - return ""; - } - - @Override - public String getDailyTip() { - return dailyTip; - } + record FakeHomeRepository(String dailyTip, List apps) implements HomeRepository { @Override - public void fetchPromotedApps(PromotedAppsCallback callback) { - callback.onResult(apps); + public String getPlayStoreUrl() { + return ""; + } + + @Override + public String getAppPlayStoreUrl(String packageName) { + return ""; + } + + @Override + public void fetchPromotedApps(PromotedAppsCallback callback) { + callback.onResult(apps); + } } - } }