11package com .codetrio .spatialflow ;
22
3+ import android .Manifest ;
4+ import android .content .pm .PackageManager ;
35import android .content .res .ColorStateList ;
46import android .content .res .Configuration ;
57import android .os .Build ;
68import android .os .Bundle ;
7- import android .util .Log ;
89import android .view .ViewGroup ;
910import android .view .Window ;
10- import android .widget .Toast ;
1111
1212import androidx .annotation .NonNull ;
1313import androidx .appcompat .app .AppCompatActivity ;
14+ import androidx .core .app .ActivityCompat ;
1415import androidx .core .content .ContextCompat ;
1516import androidx .core .graphics .Insets ;
1617import androidx .core .view .ViewCompat ;
1718import androidx .core .view .WindowCompat ;
1819import androidx .core .view .WindowInsetsCompat ;
1920import androidx .core .view .WindowInsetsControllerCompat ;
20- import androidx .recyclerview .widget .RecyclerView ;
2121import androidx .navigation .NavController ;
2222import androidx .navigation .NavDestination ;
2323import androidx .navigation .NavOptions ;
3030public class MainActivity extends AppCompatActivity {
3131
3232 private static final String TAG = "MainActivity" ;
33+ private static final int AUDIO_PERMISSION_REQUEST = 100 ;
34+
3335 private BottomNavigationView navView ;
3436 private NavController navController ;
3537 private int previousDestination = R .id .navigation_player ; // default
3638
3739 @ Override
3840 protected void onCreate (Bundle savedInstanceState ) {
39- // ⭐ ENABLE DYNAMIC COLORS FIRST - This is the key!
41+ // Enable dynamic colors
4042 DynamicColors .applyToActivityIfAvailable (this );
4143
4244 super .onCreate (savedInstanceState );
4345
4446 setupSystemBars ();
4547 setContentView (R .layout .activity_main );
4648
49+ // Check audio/media permissions for MediaStore song list
50+ checkAudioPermission ();
51+
4752 navView = findViewById (R .id .nav_view );
4853 navController = Navigation .findNavController (this , R .id .nav_host_fragment_activity_main );
4954
50- // apply insets so navView doesn't overlap app content and top area is safe
5155 applyWindowInsetsBehavior ();
52-
53- // color the bottom nav icons/text according to theme - NOW USING DYNAMIC COLORS
5456 setupBottomNavColors (navView );
5557
56- // wire up Navigation component
5758 NavigationUI .setupWithNavController (navView , navController );
5859
59- // override selection to provide custom backstack behavior + animated transitions
6060 navView .setOnItemSelectedListener (item -> {
6161 int destId = item .getItemId ();
6262 NavDestination current = navController .getCurrentDestination ();
6363 if (current != null && current .getId () == destId ) return true ;
6464
6565 NavOptions navOptions = getNavOptions (previousDestination , destId );
6666
67- // Pop to start destination, then navigate so each tab behaves like top-level
6867 navController .popBackStack (navController .getGraph ().getStartDestinationId (), false );
6968 navController .navigate (destId , null , navOptions );
7069
@@ -76,89 +75,90 @@ protected void onCreate(Bundle savedInstanceState) {
7675 // optional: scroll to top or refresh
7776 });
7877
79- // ensure initial selected item
8078 if (savedInstanceState == null ) {
8179 navController .navigate (R .id .navigation_player );
8280 navView .setSelectedItemId (R .id .navigation_player );
8381 }
82+ }
8483
85- // (Optional) start background service if your app requires it (uncomment if needed)
86- // startYourService();
84+ private void checkAudioPermission () {
85+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
86+ if (ContextCompat .checkSelfPermission (this , Manifest .permission .READ_MEDIA_AUDIO )
87+ != PackageManager .PERMISSION_GRANTED ) {
88+ ActivityCompat .requestPermissions (
89+ this ,
90+ new String []{Manifest .permission .READ_MEDIA_AUDIO },
91+ AUDIO_PERMISSION_REQUEST
92+ );
93+ }
94+ } else {
95+ if (ContextCompat .checkSelfPermission (this , Manifest .permission .READ_EXTERNAL_STORAGE )
96+ != PackageManager .PERMISSION_GRANTED ) {
97+ ActivityCompat .requestPermissions (
98+ this ,
99+ new String []{Manifest .permission .READ_EXTERNAL_STORAGE },
100+ AUDIO_PERMISSION_REQUEST
101+ );
102+ }
103+ }
87104 }
88105
89106 private void applyWindowInsetsBehavior () {
90107 ViewCompat .setOnApplyWindowInsetsListener (findViewById (R .id .container ), (v , insets ) -> {
91108 Insets sys = insets .getInsets (WindowInsetsCompat .Type .systemBars ());
92-
93- // add top padding if you want content below status bar (optional)
94109 v .setPadding (v .getPaddingLeft (), sys .top , v .getPaddingRight (), v .getPaddingBottom ());
95110 return insets ;
96111 });
97112
98- // apply bottom navigation margin equal to nav bar inset so it sits above system nav
99113 ViewCompat .setOnApplyWindowInsetsListener (navView , (v , insets ) -> {
100114 Insets sys = insets .getInsets (WindowInsetsCompat .Type .systemBars ());
101115
102116 ViewGroup .MarginLayoutParams params = (ViewGroup .MarginLayoutParams ) v .getLayoutParams ();
103117 params .bottomMargin = sys .bottom ;
104118 v .setLayoutParams (params );
105-
106- // ensure bottom nav is above content visually
107119 v .bringToFront ();
108120 return insets ;
109121 });
110122
111- // ALSO pad inner NavHostFragment's RecyclerView if it exists (not always necessary, but safe)
112- ViewCompat .setOnApplyWindowInsetsListener (findViewById (R .id .nav_host_fragment_activity_main ), (v , insets ) -> {
113- Insets sys = insets .getInsets (WindowInsetsCompat .Type .systemBars ());
114- // find inner recycler view if needed later (fragments may host it)
115- return insets ;
116- });
123+ ViewCompat .setOnApplyWindowInsetsListener (
124+ findViewById (R .id .nav_host_fragment_activity_main ),
125+ (v , insets ) -> insets
126+ );
117127
118- // trigger insets dispatch
119128 findViewById (R .id .container ).requestApplyInsets ();
120129 }
121130
122131 private void setupBottomNavColors (BottomNavigationView navView ) {
123- // ⭐ USE THEME ATTRIBUTES INSTEAD OF HARDCODED COLORS
124- // This allows dynamic colors to work properly
125-
126- // Get colors from the current theme (which has dynamic colors applied)
127132 android .util .TypedValue typedValue = new android .util .TypedValue ();
128133 android .content .res .Resources .Theme theme = getTheme ();
129134
130- // Get active color (selected state) - use onSecondaryContainer for better contrast
131135 theme .resolveAttribute (com .google .android .material .R .attr .colorOnSecondaryContainer , typedValue , true );
132136 int activeColor = typedValue .data ;
133137
134- // Get inactive color (unselected state)
135138 theme .resolveAttribute (com .google .android .material .R .attr .colorOnSurfaceVariant , typedValue , true );
136139 int inactiveColor = typedValue .data ;
137140
138- // Get background color
139141 theme .resolveAttribute (com .google .android .material .R .attr .colorSurfaceContainer , typedValue , true );
140142 int backgroundColor = typedValue .data ;
141143
142- // ---- Create ColorStateList for icons ----
143144 ColorStateList iconColorStateList = new ColorStateList (
144145 new int [][]{
145- new int []{android .R .attr .state_checked }, // selected
146- new int []{-android .R .attr .state_checked } // unselected
146+ new int []{android .R .attr .state_checked },
147+ new int []{-android .R .attr .state_checked }
147148 },
148149 new int []{
149150 activeColor ,
150151 inactiveColor
151152 }
152153 );
153154
154- // ---- Create ColorStateList for text (uses onSurface for better visibility) ----
155155 theme .resolveAttribute (com .google .android .material .R .attr .colorOnSurface , typedValue , true );
156156 int activeTextColor = typedValue .data ;
157157
158158 ColorStateList textColorStateList = new ColorStateList (
159159 new int [][]{
160- new int []{android .R .attr .state_checked }, // selected
161- new int []{-android .R .attr .state_checked } // unselected
160+ new int []{android .R .attr .state_checked },
161+ new int []{-android .R .attr .state_checked }
162162 },
163163 new int []{
164164 activeTextColor ,
@@ -168,11 +168,7 @@ private void setupBottomNavColors(BottomNavigationView navView) {
168168
169169 navView .setItemIconTintList (iconColorStateList );
170170 navView .setItemTextColor (textColorStateList );
171-
172- // ---- BACKGROUND COLOR (uses Material3 dynamic color) ----
173171 navView .setBackgroundColor (backgroundColor );
174-
175- // ---- Material3 icon size ----
176172 navView .setItemIconSize ((int ) (28 * getResources ().getDisplayMetrics ().density ));
177173 }
178174
@@ -184,9 +180,9 @@ private void setupSystemBars() {
184180
185181 WindowCompat .setDecorFitsSystemWindows (window , false );
186182
187- WindowInsetsControllerCompat insetsController = WindowCompat .getInsetsController (window , window .getDecorView ());
183+ WindowInsetsControllerCompat insetsController =
184+ WindowCompat .getInsetsController (window , window .getDecorView ());
188185
189- // make status/nav bar transparent so app draws behind them (we handle padding)
190186 window .setStatusBarColor (android .graphics .Color .TRANSPARENT );
191187 window .setNavigationBarColor (android .graphics .Color .TRANSPARENT );
192188
@@ -228,4 +224,4 @@ private int getDestinationIndex(int id) {
228224 public boolean onSupportNavigateUp () {
229225 return navController .navigateUp () || super .onSupportNavigateUp ();
230226 }
231- }
227+ }
0 commit comments