diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0f378f7..68a1defe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -304,6 +304,26 @@ android:label="@string/navigation_drawer" android:parentActivityName=".ui.screens.main.MainActivity" /> + + + + + + + + diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/notifications/workers/QuizReminderWorker.java b/app/src/main/java/com/d4rk/androidtutorials/java/notifications/workers/QuizReminderWorker.java index 42ac40be..356a12dd 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/notifications/workers/QuizReminderWorker.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/notifications/workers/QuizReminderWorker.java @@ -4,17 +4,17 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; -import androidx.navigation.NavDeepLinkBuilder; import androidx.work.Worker; import androidx.work.WorkerParameters; import com.d4rk.androidtutorials.java.R; -import com.d4rk.androidtutorials.java.ui.screens.main.MainActivity; +import com.d4rk.androidtutorials.java.ui.screens.quiz.QuizActivity; /** * Worker that displays the daily quiz reminder notification. @@ -39,11 +39,9 @@ public Result doWork() { ); manager.createNotificationChannel(channel); - PendingIntent pendingIntent = new NavDeepLinkBuilder(getApplicationContext()) - .setComponentName(MainActivity.class) - .setGraph(R.navigation.mobile_navigation) - .setDestination(R.id.navigation_quiz) - .createPendingIntent(); + Intent intent = new Intent(getApplicationContext(), QuizActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity( + getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId) .setSmallIcon(R.drawable.ic_check_circle) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/components/navigation/BottomSheetMenuFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/components/navigation/BottomSheetMenuFragment.java index 5471f37e..0f60de3c 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/components/navigation/BottomSheetMenuFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/components/navigation/BottomSheetMenuFragment.java @@ -12,7 +12,7 @@ import com.d4rk.androidtutorials.java.BuildConfig; import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.databinding.BottomSheetMenuBinding; -import androidx.navigation.fragment.NavHostFragment; +import com.d4rk.androidtutorials.java.ui.screens.settings.SettingsActivity; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; public class BottomSheetMenuFragment extends BottomSheetDialogFragment { @@ -26,12 +26,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, binding = BottomSheetMenuBinding.inflate(inflater, container, false); binding.menuSettings.setOnClickListener(v -> { - NavHostFragment navHostFragment = (NavHostFragment) requireActivity() - .getSupportFragmentManager() - .findFragmentById(R.id.nav_host_fragment_activity_main); - if (navHostFragment != null) { - navHostFragment.getNavController().navigate(R.id.navigation_settings); - } + Intent intent = new Intent(requireContext(), SettingsActivity.class); + startActivity(intent); dismiss(); }); 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 28d6de40..69803624 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 @@ -35,6 +35,7 @@ import com.d4rk.androidtutorials.java.ui.components.navigation.BottomSheetMenuFragment; import com.d4rk.androidtutorials.java.ui.screens.startup.StartupViewModel; import com.d4rk.androidtutorials.java.ui.screens.startup.dialogs.ConsentDialogFragment; +import com.d4rk.androidtutorials.java.ui.screens.support.SupportActivity; import com.d4rk.androidtutorials.java.utils.ConsentUtils; import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; import com.google.android.gms.ads.AdRequest; @@ -256,9 +257,7 @@ public boolean onCreateOptionsMenu(android.view.Menu menu) { @Override public boolean onOptionsItemSelected(android.view.MenuItem item) { if (item.getItemId() == R.id.support) { - if (navController != null) { - navController.navigate(R.id.navigation_support); - } + startActivity(new Intent(this, SupportActivity.class)); return true; } return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java similarity index 69% rename from app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizFragment.java rename to app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java index d771d234..2dfc4ac2 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizActivity.java @@ -1,54 +1,60 @@ package com.d4rk.androidtutorials.java.ui.screens.quiz; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; +import android.view.MenuItem; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.fragment.NavHostFragment; -import com.airbnb.lottie.LottieAnimationView; import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.data.model.QuizQuestion; -import com.d4rk.androidtutorials.java.databinding.FragmentQuizBinding; +import com.d4rk.androidtutorials.java.databinding.ActivityQuizBinding; import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import com.airbnb.lottie.LottieAnimationView; +/** + * Activity that displays a simple multiple-choice quiz. + */ import dagger.hilt.android.AndroidEntryPoint; @AndroidEntryPoint -public class QuizFragment extends Fragment { +public class QuizActivity extends AppCompatActivity { - private FragmentQuizBinding binding; + private ActivityQuizBinding binding; private QuizViewModel viewModel; - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - binding = FragmentQuizBinding.inflate(inflater, container, false); + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityQuizBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); - EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(requireActivity()); + EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this); edgeToEdgeDelegate.applyEdgeToEdge(binding.container); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + viewModel = new ViewModelProvider(this).get(QuizViewModel.class); if (viewModel.getTotalQuestions() == 0) { - new MaterialAlertDialogBuilder(requireContext()) + new MaterialAlertDialogBuilder(this) .setMessage(R.string.quiz_no_more_questions) - .setPositiveButton(android.R.string.ok, (d, w) -> NavHostFragment.findNavController(this).popBackStack()) + .setPositiveButton(android.R.string.ok, (d, w) -> finish()) .setCancelable(false) .show(); - } else { - showQuestion(viewModel.getCurrentQuestion()); - binding.buttonNext.setOnClickListener(v -> onNextClicked()); + return; } + showQuestion(viewModel.getCurrentQuestion()); - return binding.getRoot(); + binding.buttonNext.setOnClickListener(v -> onNextClicked()); } private void onNextClicked() { @@ -88,21 +94,24 @@ private void showQuestion(QuizQuestion question) { private void showResult() { int score = viewModel.getScore().getValue(); int total = viewModel.getTotalQuestions(); - View view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_quiz_result, null, false); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_quiz_result, null, false); TextView textResult = view.findViewById(R.id.text_result); textResult.setText(getString(R.string.quiz_finished, score, total)); LottieAnimationView animationView = view.findViewById(R.id.animation_success); animationView.playAnimation(); - new MaterialAlertDialogBuilder(requireContext()) + new MaterialAlertDialogBuilder(this) .setView(view) - .setPositiveButton(android.R.string.ok, (d, w) -> NavHostFragment.findNavController(this).popBackStack()) + .setPositiveButton(android.R.string.ok, (d, w) -> finish()) .setCancelable(false) .show(); } @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); } } diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/SettingsActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/SettingsActivity.java new file mode 100644 index 00000000..2d52301d --- /dev/null +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/SettingsActivity.java @@ -0,0 +1,84 @@ +package com.d4rk.androidtutorials.java.ui.screens.settings; + +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; +import androidx.preference.ListPreference; + +import com.d4rk.androidtutorials.java.R; +import com.d4rk.androidtutorials.java.databinding.ActivitySettingsBinding; +import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; + +/** + * Settings screen that delegates preference change logic to a ViewModel/Repository. + */ +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint +public class SettingsActivity extends AppCompatActivity + implements SharedPreferences.OnSharedPreferenceChangeListener, + androidx.preference.Preference.SummaryProvider { + + private SettingsViewModel settingsViewModel; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActivitySettingsBinding binding = ActivitySettingsBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this); + edgeToEdgeDelegate.applyEdgeToEdge(binding.container); + + settingsViewModel = new ViewModelProvider(this).get(SettingsViewModel.class); + settingsViewModel.applyConsent(); + + getSupportFragmentManager().beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + + ActionBar supportActionBar = getSupportActionBar(); + if (supportActionBar != null) { + supportActionBar.setDisplayHomeAsUpEnabled(true); + } + + settingsViewModel.registerPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + boolean changedTheme = settingsViewModel.onPreferenceChanged(key); + if (changedTheme) { + recreate(); + } + } + + /** + * Provide summary for ListPreference if needed + */ + @Override + public CharSequence provideSummary(ListPreference preference) { + String key = preference.getKey(); + if (key != null && key.equals(getString(R.string.dark_mode))) { + String value = settingsViewModel.getDarkMode(); + int index = preference.findIndexOfValue(value); + if (index >= 0) { + CharSequence[] entries = preference.getEntries(); + if (entries != null && index < entries.length) { + return entries[index]; + } + } + } + return null; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + settingsViewModel.unregisterPreferenceChangeListener(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java similarity index 74% rename from app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportFragment.java rename to app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java index 38359cd2..7b868013 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java @@ -3,18 +3,16 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; +import android.view.MenuItem; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; import com.android.billingclient.api.ProductDetails; import com.d4rk.androidtutorials.java.data.repository.SupportRepository; -import com.d4rk.androidtutorials.java.databinding.FragmentSupportBinding; +import com.d4rk.androidtutorials.java.databinding.ActivitySupportBinding; import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; import com.google.android.gms.ads.AdRequest; @@ -23,20 +21,26 @@ import dagger.hilt.android.AndroidEntryPoint; @AndroidEntryPoint -public class SupportFragment extends Fragment { +public class SupportActivity extends AppCompatActivity { - private FragmentSupportBinding binding; + private ActivitySupportBinding binding; private SupportViewModel supportViewModel; - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - binding = FragmentSupportBinding.inflate(inflater, container, false); + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivitySupportBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); - EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(requireActivity()); + + EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this); edgeToEdgeDelegate.applyEdgeToEdge(binding.container); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + supportViewModel = new ViewModelProvider(this).get(SupportViewModel.class); AdRequest adRequest = supportViewModel.initMobileAds(); @@ -51,8 +55,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.buttonNormalDonation.setOnClickListener(v -> initiatePurchase("normal_donation")); binding.buttonHighDonation.setOnClickListener(v -> initiatePurchase("high_donation")); binding.buttonExtremeDonation.setOnClickListener(v -> initiatePurchase("extreme_donation")); - - return binding.getRoot(); } private void queryProductDetails() { @@ -75,13 +77,16 @@ private void queryProductDetails() { private void initiatePurchase(String productId) { SupportRepository.BillingFlowLauncher launcher = supportViewModel.initiatePurchase(productId); if (launcher != null) { - launcher.launch(requireActivity()); + launcher.launch(this); } } @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); } -} +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_quiz.xml b/app/src/main/res/layout/activity_quiz.xml similarity index 100% rename from app/src/main/res/layout/fragment_quiz.xml rename to app/src/main/res/layout/activity_quiz.xml diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 00000000..ec7d0231 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_support.xml b/app/src/main/res/layout/activity_support.xml similarity index 100% rename from app/src/main/res/layout/fragment_support.xml rename to app/src/main/res/layout/activity_support.xml diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 98f3f661..58964ad1 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -17,24 +17,4 @@ android:name="com.d4rk.androidtutorials.java.ui.screens.about.AboutFragment" android:label="@string/about" tools:layout="@layout/fragment_about" /> - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/xml-v26/shortcuts.xml b/app/src/main/res/xml-v26/shortcuts.xml index 74cba319..50beaf4d 100644 --- a/app/src/main/res/xml-v26/shortcuts.xml +++ b/app/src/main/res/xml-v26/shortcuts.xml @@ -7,8 +7,7 @@ android:shortcutShortLabel="@string/settings"> \ No newline at end of file