Skip to content

Commit 3105136

Browse files
Merge branch 'main' into codex/add-more-unit-tests
2 parents 55211a0 + 2226a32 commit 3105136

File tree

110 files changed

+369
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+369
-269
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Version 5.0.2:
22

3+
- **New**: Added a Room database lesson demonstrating entity & DAO.
34
- **New**: Added a new lesson on using bottom navigation.
45
- **New**: Added a new lesson on using navigation drawer.
56
- **Major**: Introduced new font styling and improved code visualization.
67
- **Minor**: Set **Google Sans Code** as the default font.
78
- **Minor**: Added a search function for lessons.
89
- **Patch**: Optimized app performance for smoother operation.
910
- **Patch**: Updated several components to improve compatibility.
10-
- **New**: Added a Room database lesson demonstrating entity, DAO, and code viewer integration.
1111

1212
# Version 5.0.1:
1313

app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ dependencies {
6060
implementation libs.review
6161
implementation libs.app.update
6262
implementation libs.volley
63+
implementation libs.glide
6364

6465
// Firebase
6566
implementation platform(libs.firebase.bom)
@@ -98,6 +99,7 @@ dependencies {
9899
annotationProcessor libs.hilt.compiler
99100
implementation libs.androidx.room.runtime
100101
annotationProcessor libs.androidx.room.compiler
102+
annotationProcessor libs.glide.compiler
101103

102104
// Testing
103105
testImplementation libs.junit

app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAd.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.app.Application.ActivityLifecycleCallbacks;
66
import android.content.Context;
77
import android.os.Bundle;
8+
import android.webkit.CookieManager;
89

910
import androidx.annotation.NonNull;
1011
import androidx.annotation.Nullable;
@@ -20,10 +21,10 @@
2021
import com.google.android.gms.ads.MobileAds;
2122
import com.google.android.gms.ads.appopen.AppOpenAd.AppOpenAdLoadCallback;
2223

23-
import dagger.hilt.android.HiltAndroidApp;
24-
2524
import java.util.Date;
2625

26+
import dagger.hilt.android.HiltAndroidApp;
27+
2728
@SuppressWarnings("ALL")
2829
@HiltAndroidApp
2930
public class AppOpenAd extends Application implements ActivityLifecycleCallbacks, LifecycleObserver {
@@ -33,11 +34,14 @@ public class AppOpenAd extends Application implements ActivityLifecycleCallbacks
3334
@Override
3435
public void onCreate() {
3536
super.onCreate();
36-
this.registerActivityLifecycleCallbacks(this);
37-
MobileAds.initialize(this, initializationStatus -> {
38-
});
37+
registerActivityLifecycleCallbacks(this);
38+
MobileAds.initialize(
39+
this,
40+
initializationStatus -> {
41+
});
42+
CookieManager.getInstance();
3943
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
40-
appOpenAdManager = new AppOpenAdManager();
44+
appOpenAdManager = new AppOpenAdManager(this);
4145
}
4246

4347
@OnLifecycleEvent(Event.ON_START)
@@ -90,8 +94,10 @@ private static class AppOpenAdManager {
9094
private boolean isLoadingAd = false;
9195
private boolean isShowingAd = false;
9296
private long loadTime = 0;
97+
private final Application application;
9398

94-
public AppOpenAdManager() {
99+
public AppOpenAdManager(Application application) {
100+
this.application = application;
95101
}
96102

97103
private void loadAd(Context context) {
@@ -140,7 +146,7 @@ private void showAdIfAvailable(
140146
}
141147
if (!isAdAvailable()) {
142148
onShowAdCompleteListener.onShowAdComplete();
143-
loadAd(activity);
149+
loadAd(application.getApplicationContext());
144150
return;
145151
}
146152
appOpenAd.setFullScreenContentCallback(
@@ -150,15 +156,15 @@ public void onAdDismissedFullScreenContent() {
150156
appOpenAd = null;
151157
isShowingAd = false;
152158
onShowAdCompleteListener.onShowAdComplete();
153-
loadAd(activity);
159+
loadAd(application.getApplicationContext());
154160
}
155161

156162
@Override
157163
public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) {
158164
appOpenAd = null;
159165
isShowingAd = false;
160166
onShowAdCompleteListener.onShowAdComplete();
161-
loadAd(activity);
167+
loadAd(application.getApplicationContext());
162168
}
163169

164170
@Override
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.d4rk.androidtutorials.java.ui.components.navigation;
2+
3+
import android.os.Bundle;
4+
import android.view.View;
5+
6+
import androidx.annotation.Nullable;
7+
import androidx.appcompat.app.ActionBar;
8+
import androidx.appcompat.app.AppCompatActivity;
9+
10+
import com.d4rk.androidtutorials.java.R;
11+
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
12+
13+
public abstract class BaseActivity extends AppCompatActivity {
14+
15+
@Override
16+
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
17+
super.onPostCreate(savedInstanceState);
18+
View container = findViewById(R.id.container);
19+
if (container != null) {
20+
EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this);
21+
edgeToEdgeDelegate.applyEdgeToEdge(container);
22+
}
23+
ActionBar actionBar = getSupportActionBar();
24+
if (actionBar != null) {
25+
actionBar.setDisplayHomeAsUpEnabled(true);
26+
}
27+
}
28+
29+
@Override
30+
public boolean onSupportNavigateUp() {
31+
finish();
32+
return true;
33+
}
34+
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/help/HelpActivity.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
import android.view.MenuItem;
1010

1111
import androidx.annotation.NonNull;
12-
import androidx.appcompat.app.ActionBar;
1312
import androidx.appcompat.app.AlertDialog;
14-
import com.d4rk.androidtutorials.java.ui.components.navigation.UpNavigationActivity;
13+
import com.d4rk.androidtutorials.java.ui.components.navigation.BaseActivity;
1514
import androidx.lifecycle.ViewModelProvider;
1615
import androidx.preference.Preference;
1716
import androidx.preference.PreferenceFragmentCompat;
@@ -21,7 +20,6 @@
2120
import com.d4rk.androidtutorials.java.databinding.ActivityHelpBinding;
2221
import com.d4rk.androidtutorials.java.databinding.DialogVersionInfoBinding;
2322
import com.d4rk.androidtutorials.java.ui.screens.help.repository.HelpRepository;
24-
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
2523
import com.d4rk.androidtutorials.java.utils.OpenSourceLicensesUtils;
2624
import com.google.android.material.snackbar.Snackbar;
2725
import com.google.android.play.core.review.ReviewInfo;
@@ -30,7 +28,7 @@
3028
import dagger.hilt.android.AndroidEntryPoint;
3129

3230
@AndroidEntryPoint
33-
public class HelpActivity extends UpNavigationActivity {
31+
public class HelpActivity extends BaseActivity {
3432

3533
private HelpViewModel helpViewModel;
3634

@@ -40,16 +38,8 @@ protected void onCreate(Bundle savedInstanceState) {
4038
ActivityHelpBinding binding = ActivityHelpBinding.inflate(getLayoutInflater());
4139
setContentView(binding.getRoot());
4240

43-
EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this);
44-
edgeToEdgeDelegate.applyEdgeToEdge(binding.container);
45-
4641
helpViewModel = new ViewModelProvider(this).get(HelpViewModel.class);
4742

48-
ActionBar actionBar = getSupportActionBar();
49-
if (actionBar != null) {
50-
actionBar.setDisplayHomeAsUpEnabled(true);
51-
}
52-
5343
getSupportFragmentManager().beginTransaction()
5444
.replace(R.id.frame_layout_faq, new FaqFragment())
5545
.commit();
@@ -73,7 +63,10 @@ public boolean onCreateOptionsMenu(Menu menu) {
7363
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
7464
int itemId = item.getItemId();
7565

76-
if (itemId == R.id.view_in_google_play) {
66+
if (itemId == android.R.id.home) {
67+
finish();
68+
return true;
69+
} else if (itemId == R.id.view_in_google_play) {
7770
openGooglePlayListing();
7871
return true;
7972
} else if (itemId == R.id.version_info) {

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import me.zhanghai.android.fastscroll.FastScrollerBuilder;
1818

19+
import com.bumptech.glide.Glide;
1920
import dagger.hilt.android.AndroidEntryPoint;
2021

2122
@AndroidEntryPoint
@@ -103,14 +104,9 @@ private void shareApp(com.d4rk.androidtutorials.java.data.model.PromotedApp app)
103104
}
104105

105106
private void loadImage(String url, android.widget.ImageView imageView) {
106-
com.android.volley.toolbox.ImageRequest request = new com.android.volley.toolbox.ImageRequest(
107-
url,
108-
imageView::setImageBitmap,
109-
0,
110-
0,
111-
android.widget.ImageView.ScaleType.CENTER_INSIDE,
112-
android.graphics.Bitmap.Config.ARGB_8888,
113-
error -> {});
114-
com.android.volley.toolbox.Volley.newRequestQueue(requireContext()).add(request);
107+
Glide.with(imageView.getContext())
108+
.load(url)
109+
.centerInside()
110+
.into(imageView);
115111
}
116112
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import android.util.Log;
99
import android.util.SparseIntArray;
1010
import android.view.View;
11+
import android.widget.Toast;
1112

1213
import androidx.activity.OnBackPressedCallback;
1314
import androidx.activity.result.ActivityResultLauncher;
@@ -42,7 +43,6 @@
4243
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
4344
import com.google.android.gms.ads.AdRequest;
4445
import com.google.android.gms.ads.MobileAds;
45-
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
4646
import com.google.android.material.navigation.NavigationBarView;
4747
import com.google.android.material.navigationrail.NavigationRailView;
4848
import com.google.android.material.snackbar.Snackbar;
@@ -80,6 +80,8 @@ public class MainActivity extends AppCompatActivity {
8080
private AppUpdateNotificationsManager appUpdateNotificationsManager;
8181
private AppUpdateManager appUpdateManager;
8282
private InstallStateUpdatedListener installStateUpdatedListener;
83+
private long backPressedTime;
84+
private static final long BACK_PRESS_INTERVAL = 2000;
8385
private final DefaultLifecycleObserver lifecycleObserver = new DefaultLifecycleObserver() {
8486
@Override
8587
public void onResume(@NonNull LifecycleOwner owner) {
@@ -88,11 +90,13 @@ public void onResume(@NonNull LifecycleOwner owner) {
8890
if (ConsentUtils.canShowAds(MainActivity.this)) {
8991
if (mBinding.adView.getVisibility() != View.VISIBLE) {
9092
MobileAds.initialize(MainActivity.this);
93+
mBinding.adPlaceholder.setVisibility(View.GONE);
9194
mBinding.adView.setVisibility(View.VISIBLE);
9295
mBinding.adView.loadAd(new AdRequest.Builder().build());
9396
}
9497
} else {
9598
mBinding.adView.setVisibility(View.GONE);
99+
mBinding.adPlaceholder.setVisibility(View.VISIBLE);
96100
}
97101
}
98102
}
@@ -149,15 +153,15 @@ protected void onCreate(Bundle savedInstanceState) {
149153
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
150154
@Override
151155
public void handleOnBackPressed() {
152-
new MaterialAlertDialogBuilder(MainActivity.this)
153-
.setTitle(R.string.alert_dialog_close)
154-
.setMessage(R.string.summary_alert_dialog_close)
155-
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
156-
finish();
157-
moveTaskToBack(true);
158-
})
159-
.setNegativeButton(android.R.string.no, null)
160-
.show(); }
156+
long currentTime = System.currentTimeMillis();
157+
if (currentTime - backPressedTime < BACK_PRESS_INTERVAL) {
158+
finish();
159+
moveTaskToBack(true);
160+
} else {
161+
backPressedTime = currentTime;
162+
Toast.makeText(MainActivity.this, R.string.press_back_again_to_exit, Toast.LENGTH_SHORT).show();
163+
}
164+
}
161165
});
162166
}
163167

@@ -214,10 +218,12 @@ private void observeViewModel() {
214218
if (mBinding.adView != null) {
215219
if (ConsentUtils.canShowAds(this)) {
216220
MobileAds.initialize(this);
221+
mBinding.adPlaceholder.setVisibility(View.GONE);
217222
mBinding.adView.setVisibility(View.VISIBLE);
218223
mBinding.adView.loadAd(new AdRequest.Builder().build());
219224
} else {
220225
mBinding.adView.setVisibility(View.GONE);
226+
mBinding.adPlaceholder.setVisibility(View.VISIBLE);
221227
}
222228
}
223229
}
@@ -287,6 +293,9 @@ private void observeViewModel() {
287293
recreate();
288294
}
289295
});
296+
297+
mainViewModel.getLoadingState().observe(this, isLoading ->
298+
mBinding.progressBar.setVisibility(Boolean.TRUE.equals(isLoading) ? View.VISIBLE : View.GONE));
290299
}
291300

292301
private void setupUpdateNotifications() {

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainViewModel.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class MainViewModel extends ViewModel {
3939
private final BuildShortcutIntentUseCase buildShortcutIntentUseCase;
4040
private final GetAppUpdateManagerUseCase getAppUpdateManagerUseCase;
4141
private final MutableLiveData<MainUiState> uiState = new MutableLiveData<>();
42+
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
4243

4344
@Inject
4445
public MainViewModel(ApplyThemeSettingsUseCase applyThemeSettingsUseCase,
@@ -80,6 +81,7 @@ public MainViewModel(ApplyThemeSettingsUseCase applyThemeSettingsUseCase,
8081
public void applySettings(String[] themeValues,
8182
String[] bottomNavBarLabelsValues,
8283
String[] defaultTabValues) {
84+
isLoading.setValue(true);
8385
boolean changedTheme = applyThemeSettingsUseCase.invoke(themeValues);
8486

8587
String labelVisibilityStr = getBottomNavLabelVisibilityUseCase.invoke();
@@ -99,6 +101,7 @@ public void applySettings(String[] themeValues,
99101

100102
uiState.setValue(new MainUiState(visibilityMode, startFragmentId, changedTheme));
101103
applyLanguageSettingsUseCase.invoke();
104+
isLoading.setValue(false);
102105
}
103106

104107
/**
@@ -136,6 +139,13 @@ public LiveData<MainUiState> getUiState() {
136139
return uiState;
137140
}
138141

142+
/**
143+
* Expose loading state to toggle progress indicators.
144+
*/
145+
public LiveData<Boolean> getLoadingState() {
146+
return isLoading;
147+
}
148+
139149
/**
140150
* Expose the AppUpdateManager if the Activity wants to directly check for in-app updates.
141151
*/

0 commit comments

Comments
 (0)