1919import androidx .annotation .NonNull ;
2020import androidx .annotation .Nullable ;
2121import androidx .appcompat .widget .SearchView ;
22+ import androidx .core .view .MenuHost ;
23+ import androidx .core .view .MenuProvider ;
2224import androidx .fragment .app .Fragment ;
25+ import androidx .lifecycle .Lifecycle ;
26+ import androidx .recyclerview .widget .DiffUtil ;
2327import androidx .recyclerview .widget .LinearLayoutManager ;
2428import androidx .recyclerview .widget .RecyclerView ;
2529
4145import java .util .Collections ;
4246import java .util .HashSet ;
4347import java .util .List ;
48+ import java .util .Objects ;
4449import java .util .Random ;
4550import java .util .Set ;
4651
@@ -54,7 +59,6 @@ public class AndroidStudioFragment extends Fragment {
5459 @ Override
5560 public View onCreateView (@ NonNull LayoutInflater inflater , @ Nullable ViewGroup container ,
5661 @ Nullable Bundle savedInstanceState ) {
57- setHasOptionsMenu (true );
5862 return inflater .inflate (R .layout .fragment_android_studio , container , false );
5963 }
6064
@@ -70,6 +74,49 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
7074 allItems .clear ();
7175 allItems .addAll (loadItems ());
7276 populateAdapter (allItems );
77+ 78+ MenuHost menuHost = requireActivity ();
79+ menuHost .addMenuProvider (new MenuProvider () {
80+ @ Override
81+ public void onCreateMenu (@ NonNull Menu menu , @ NonNull MenuInflater menuInflater ) {
82+ menuInflater .inflate (R .menu .menu_android_studio , menu );
83+ MenuItem searchItem = menu .findItem (R .id .action_search );
84+ SearchView searchView = (SearchView ) searchItem .getActionView ();
85+ if (searchView != null ) {
86+ searchView .setQueryHint (getString (R .string .search_lessons_hint ));
87+ searchView .setOnQueryTextListener (new SearchView .OnQueryTextListener () {
88+ @ Override
89+ public boolean onQueryTextSubmit (String query ) {
90+ filterLessons (query );
91+ return true ;
92+ }
93+ 94+ @ Override
95+ public boolean onQueryTextChange (String newText ) {
96+ filterLessons (newText );
97+ return true ;
98+ }
99+ });
100+ }
101+ searchItem .setOnActionExpandListener (new MenuItem .OnActionExpandListener () {
102+ @ Override
103+ public boolean onMenuItemActionExpand (@ NonNull MenuItem item ) {
104+ return true ;
105+ }
106+ 107+ @ Override
108+ public boolean onMenuItemActionCollapse (@ NonNull MenuItem item ) {
109+ filterLessons ("" );
110+ return true ;
111+ }
112+ });
113+ }
114+ 115+ @ Override
116+ public boolean onMenuItemSelected (@ NonNull MenuItem menuItem ) {
117+ return false ;
118+ }
119+ }, getViewLifecycleOwner (), Lifecycle .State .RESUMED );
73120 }
74121
75122 private void ensureMobileAdsInitialized () {
@@ -81,8 +128,7 @@ private void ensureMobileAdsInitialized() {
81128
82129 private List <Object > loadItems () {
83130 List <Object > items = new ArrayList <>();
84- XmlResourceParser parser = getResources ().getXml (R .xml .preferences_android_studio );
85- try {
131+ try (XmlResourceParser parser = getResources ().getXml (R .xml .preferences_android_studio )) {
86132 int event = parser .getEventType ();
87133 Lesson currentLesson = null ;
88134 while (event != XmlPullParser .END_DOCUMENT ) {
@@ -124,8 +170,6 @@ private List<Object> loadItems() {
124170 event = parser .next ();
125171 }
126172 } catch (XmlPullParserException | IOException ignored ) {
127- } finally {
128- parser .close ();
129173 }
130174 return items ;
131175 }
@@ -167,40 +211,6 @@ private void populateAdapter(List<Object> source) {
167211 adapter .setItems (items );
168212 }
169213
170- @ Override
171- public void onCreateOptionsMenu (@ NonNull Menu menu , @ NonNull MenuInflater inflater ) {
172- super .onCreateOptionsMenu (menu , inflater );
173- inflater .inflate (R .menu .menu_android_studio , menu );
174- MenuItem searchItem = menu .findItem (R .id .action_search );
175- SearchView searchView = (SearchView ) searchItem .getActionView ();
176- searchView .setQueryHint (getString (R .string .search_lessons_hint ));
177- searchView .setOnQueryTextListener (new SearchView .OnQueryTextListener () {
178- @ Override
179- public boolean onQueryTextSubmit (String query ) {
180- filterLessons (query );
181- return true ;
182- }
183- 184- @ Override
185- public boolean onQueryTextChange (String newText ) {
186- filterLessons (newText );
187- return true ;
188- }
189- });
190- searchItem .setOnActionExpandListener (new MenuItem .OnActionExpandListener () {
191- @ Override
192- public boolean onMenuItemActionExpand (MenuItem item ) {
193- return true ;
194- }
195- 196- @ Override
197- public boolean onMenuItemActionCollapse (MenuItem item ) {
198- filterLessons ("" );
199- return true ;
200- }
201- });
202- }
203- 204214 private void filterLessons (String query ) {
205215 String lower = query == null ? "" : query .toLowerCase ();
206216 if (lower .isEmpty ()) {
@@ -214,8 +224,7 @@ private void filterLessons(String query) {
214224 if (item instanceof Category ) {
215225 lastCategory = (Category ) item ;
216226 categoryAdded = false ;
217- } else if (item instanceof Lesson ) {
218- Lesson l = (Lesson ) item ;
227+ } else if (item instanceof Lesson l ) {
219228 if (l .title != null && l .title .toLowerCase ().contains (lower )) {
220229 if (lastCategory != null && !categoryAdded ) {
221230 filtered .add (lastCategory );
@@ -253,11 +262,11 @@ private static class LessonAdSpacingDecoration extends RecyclerView.ItemDecorati
253262 @ Override
254263 public void getItemOffsets (@ NonNull Rect outRect , @ NonNull View view ,
255264 @ NonNull RecyclerView parent , @ NonNull RecyclerView .State state ) {
256- RecyclerView . Adapter <?> adapter = parent .getAdapter ();
257- if (!( adapter instanceof LessonsAdapter ) ) return ;
265+ LessonsAdapter adapter = ( LessonsAdapter ) parent .getAdapter ();
266+ if (adapter == null ) return ;
258267 int position = parent .getChildAdapterPosition (view );
259268 if (position == RecyclerView .NO_POSITION ) return ;
260- int type = (( LessonsAdapter ) adapter ) .getItemViewType (position );
269+ int type = adapter .getItemViewType (position );
261270 if (type == LessonsAdapter .TYPE_LESSON || type == LessonsAdapter .TYPE_AD ) {
262271 outRect .bottom = spacing ;
263272 }
@@ -271,9 +280,49 @@ private static class LessonsAdapter extends RecyclerView.Adapter<RecyclerView.Vi
271280 private final List <Object > items = new ArrayList <>();
272281
273282 void setItems (List <Object > newItems ) {
283+ DiffUtil .DiffResult diff = DiffUtil .calculateDiff (new DiffUtil .Callback () {
284+ @ Override
285+ public int getOldListSize () {
286+ return items .size ();
287+ }
288+ 289+ @ Override
290+ public int getNewListSize () {
291+ return newItems .size ();
292+ }
293+ 294+ @ Override
295+ public boolean areItemsTheSame (int oldItemPosition , int newItemPosition ) {
296+ Object oldItem = items .get (oldItemPosition );
297+ Object newItem = newItems .get (newItemPosition );
298+ if (oldItem instanceof Lesson oldLesson && newItem instanceof Lesson newLesson ) {
299+ return Objects .equals (oldLesson .title , newLesson .title );
300+ }
301+ if (oldItem instanceof Category oldCat && newItem instanceof Category newCat ) {
302+ return Objects .equals (oldCat .title , newCat .title );
303+ }
304+ return oldItem instanceof AdItem && newItem instanceof AdItem ;
305+ }
306+ 307+ @ Override
308+ public boolean areContentsTheSame (int oldItemPosition , int newItemPosition ) {
309+ Object oldItem = items .get (oldItemPosition );
310+ Object newItem = newItems .get (newItemPosition );
311+ if (oldItem instanceof Lesson oldLesson && newItem instanceof Lesson newLesson ) {
312+ return Objects .equals (oldLesson .title , newLesson .title )
313+ && Objects .equals (oldLesson .summary , newLesson .summary )
314+ && oldLesson .iconRes == newLesson .iconRes ;
315+ }
316+ if (oldItem instanceof Category oldCat && newItem instanceof Category newCat ) {
317+ return Objects .equals (oldCat .title , newCat .title )
318+ && oldCat .iconRes == newCat .iconRes ;
319+ }
320+ return true ;
321+ }
322+ });
274323 items .clear ();
275324 items .addAll (newItems );
276- notifyDataSetChanged ( );
325+ diff . dispatchUpdatesTo ( this );
277326 }
278327
279328 @ Override
@@ -307,7 +356,9 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
307356
308357 @ Override
309358 public void onBindViewHolder (@ NonNull RecyclerView .ViewHolder holder , int position ) {
310- int type = getItemViewType (position );
359+ int pos = holder .getBindingAdapterPosition ();
360+ if (pos == RecyclerView .NO_POSITION ) return ;
361+ int type = getItemViewType (pos );
311362 if (type == TYPE_AD ) {
312363 AdHolder adHolder = (AdHolder ) holder ;
313364 adHolder .adView .loadAd (new AdRequest .Builder ().build (), new AdListener () {
@@ -317,12 +368,12 @@ public void onAdFailedToLoad(@NonNull LoadAdError error) {
317368 }
318369 });
319370 } else if (type == TYPE_CATEGORY ) {
320- Category category = (Category ) items .get (position );
371+ Category category = (Category ) items .get (pos );
321372 ((CategoryHolder ) holder ).bind (category );
322373 } else {
323- Lesson lesson = (Lesson ) items .get (position );
324- boolean first = position > 0 && getItemViewType (position - 1 ) == TYPE_CATEGORY ;
325- boolean last = position == getItemCount () - 1 || getItemViewType (position + 1 ) == TYPE_CATEGORY ;
374+ Lesson lesson = (Lesson ) items .get (pos );
375+ boolean first = pos > 0 && getItemViewType (pos - 1 ) == TYPE_CATEGORY ;
376+ boolean last = pos == getItemCount () - 1 || getItemViewType (pos + 1 ) == TYPE_CATEGORY ;
326377 ((LessonHolder ) holder ).bind (lesson , first , last );
327378 }
328379 }
0 commit comments