@@ -471,8 +471,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
471471            list.add(buildItem(raw))
472472        }
473473
474-         //  ✅ Sort by normalized label
475-         list.sortWith(compareBy { normalize(getLabel(it)) })
474+         val  pinnedMap =  items.associateWith { isPinned(it) }  //  Map<T, Boolean>
475+ 
476+         //  ✅ Sort pinned first, then by normalized label
477+         list.sortWith(
478+             compareByDescending<R > { pinnedMap[items[list.indexOf(it)]] ==  true  }
479+                 .thenBy { normalize(getLabel(it)) }
480+         )
476481
477482        //  ✅ Build scroll index (safe, no `continue`)
478483        list.forEachIndexed { index, item -> 
@@ -487,8 +492,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
487492    }
488493
489494    /* *
490-      * Build app list on IO dispatcher. This function is still suspend and safe to call 
491-      * from background, but it ensures all heavy operations happen on Dispatchers.IO. 
495+      * Build app list on IO dispatcher. This function is suspend and safe to call 
496+      * from a background thread, but it ensures all heavy operations happen on Dispatchers.IO. 
497+      * 
498+      * Features: 
499+      * - Fetches recent apps and user profile apps in parallel. 
500+      * - Filters hidden/pinned apps before creating AppListItem objects. 
501+      * - Updates profile counters efficiently. 
502+      * - Optimized for memory and CPU usage on devices with many apps. 
492503     */  
493504    suspend  fun  getAppsList (
494505        context :  Context ,
@@ -503,109 +514,121 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
503514        val  seenAppKeys =  mutableSetOf<String >()
504515        val  userManager =  context.getSystemService(Context .USER_SERVICE ) as  UserManager 
505516        val  profiles =  userManager.userProfiles.toList()
517+         val  privateManager =  PrivateSpaceManager (context)
506518
507519        fun  appKey (pkg :  String , cls :  String , profileHash :  Int ) =  " $pkg |$cls |$profileHash " 
508520        fun  isHidden (pkg :  String , key :  String ) = 
509521            listOf (pkg, key, " $pkg |${key.hashCode()} " in  hiddenAppsSet }
510522
511-         AppLogger .d(
512-             " AppListDebug" 
513-             " 🔄 getAppsList called with: includeRegular=$includeRegularApps , includeHidden=$includeHiddenApps , includeRecent=$includeRecentApps " 
523+         //  Lightweight intermediate storage
524+         data class  RawApp (
525+             val  pkg :  String ,
526+             val  cls :  String ,
527+             val  label :  String ,
528+             val  user :  UserHandle ,
529+             val  profileType :  String ,
530+             val  category :  AppCategory 
514531        )
515532
516-         val  allApps  =  mutableListOf<AppListItem >()
533+         val  rawApps  =  mutableListOf<RawApp >()
517534
518-         //  🔹 Add recent  apps
535+         //  🔹 Recent  apps
519536        if  (prefs.recentAppsDisplayed &&  includeRecentApps) {
520537            runCatching {
521-                 AppUsageMonitor .createInstance(context).getLastTenAppsUsed(context).forEach { (pkg, name, activity) -> 
522-                     val  key =  appKey(pkg, activity, 0 )
523-                     if  (seenAppKeys.add(key)) {
524-                         allApps.add(
525-                             AppListItem (
526-                                 activityLabel =  name,
527-                                 activityPackage =  pkg,
528-                                 activityClass =  activity,
529-                                 user =  Process .myUserHandle(),
530-                                 profileType =  " SYSTEM" 
531-                                 customLabel =  prefs.getAppAlias(pkg).ifEmpty { name },
532-                                 customTag =  prefs.getAppTag(pkg, null ),
533-                                 category =  AppCategory .RECENT 
538+                 AppUsageMonitor .createInstance(context)
539+                     .getLastTenAppsUsed(context)
540+                     .forEach { (pkg, name, activity) -> 
541+                         val  key =  appKey(pkg, activity, 0 )
542+                         if  (seenAppKeys.add(key)) {
543+                             rawApps.add(
544+                                 RawApp (
545+                                     pkg =  pkg,
546+                                     cls =  activity,
547+                                     label =  name,
548+                                     user =  Process .myUserHandle(),
549+                                     profileType =  " SYSTEM" 
550+                                     category =  AppCategory .RECENT 
551+                                 )
534552                            )
535-                         ) 
553+                         } 
536554                    }
537-                 }
538555            }.onFailure { t -> 
539556                AppLogger .e(" AppListDebug" " Failed to add recent apps: ${t.message} " 
540557            }
541558        }
542559
543-         //  🔹 Process user profiles in parallel
560+         //  🔹 Profile apps in parallel
561+         val  launcherApps =  context.getSystemService(Context .LAUNCHER_APPS_SERVICE ) as  LauncherApps 
544562        val  deferreds =  profiles.map { profile -> 
545563            async {
546-                 val  privateManager =  PrivateSpaceManager (context)
547564                if  (privateManager.isPrivateSpaceProfile(profile) &&  privateManager.isPrivateSpaceLocked()) {
548565                    AppLogger .d(" AppListDebug" " 🔒 Skipping locked private profile: $profile " 
549-                     return @async emptyList<AppListItem >()
550-                 }
551- 
552-                 val  profileType =  when  {
553-                     privateManager.isPrivateSpaceProfile(profile) ->  " PRIVATE" 
554-                     profile !=  Process .myUserHandle() ->  " WORK" 
555-                     else  ->  " SYSTEM" 
556-                 }
557- 
558-                 val  launcherApps =  context.getSystemService(Context .LAUNCHER_APPS_SERVICE ) as  LauncherApps 
559-                 val  activities =  runCatching { launcherApps.getActivityList(null , profile) }.getOrElse {
560-                     AppLogger .e(" AppListDebug" " Failed to get activities for $profile : ${it.message} " 
561-                     emptyList()
562-                 }
566+                     emptyList<RawApp >()
567+                 } else  {
568+                     val  profileType =  when  {
569+                         privateManager.isPrivateSpaceProfile(profile) ->  " PRIVATE" 
570+                         profile !=  Process .myUserHandle() ->  " WORK" 
571+                         else  ->  " SYSTEM" 
572+                     }
563573
564-                 activities.mapNotNull { info -> 
565-                     val  pkg =  info.applicationInfo.packageName
566-                     val  cls =  info.componentName.className
567-                     val  label =  info.label.toString()
568-                     if  (pkg ==  BuildConfig .APPLICATION_ID ) return @mapNotNull null 
569- 
570-                     val  key =  appKey(pkg, cls, profile.hashCode())
571-                     if  (! seenAppKeys.add(key)) return @mapNotNull null 
572-                     if  ((isHidden(pkg, key) &&  ! includeHiddenApps) ||  (! isHidden(pkg, key) &&  ! includeRegularApps))
573-                         return @mapNotNull null 
574- 
575-                     val  category =  if  (pkg in  pinnedPackages) AppCategory .PINNED  else  AppCategory .REGULAR 
576-                     AppListItem (
577-                         activityLabel =  label,
578-                         activityPackage =  pkg,
579-                         activityClass =  cls,
580-                         user =  profile,
581-                         profileType =  profileType,
582-                         customLabel =  prefs.getAppAlias(pkg),
583-                         customTag =  prefs.getAppTag(pkg, profile),
584-                         category =  category
585-                     )
574+                     runCatching { launcherApps.getActivityList(null , profile) }
575+                         .getOrElse {
576+                             AppLogger .e(" AppListDebug" " Failed to get activities for $profile : ${it.message} " 
577+                             emptyList()
578+                         }
579+                         .mapNotNull { info -> 
580+                             val  pkg =  info.applicationInfo.packageName
581+                             val  cls =  info.componentName.className
582+                             if  (pkg ==  BuildConfig .APPLICATION_ID ) return @mapNotNull null 
583+                             val  key =  appKey(pkg, cls, profile.hashCode())
584+                             if  (! seenAppKeys.add(key)) return @mapNotNull null 
585+                             if  ((isHidden(pkg, key) &&  ! includeHiddenApps) ||  (! isHidden(pkg, key) &&  ! includeRegularApps))
586+                                 return @mapNotNull null 
587+ 
588+                             val  category =  if  (pkg in  pinnedPackages) AppCategory .PINNED  else  AppCategory .REGULAR 
589+                             RawApp (pkg, cls, info.label.toString(), profile, profileType, category)
590+                         }
586591                }
587592            }
588593        }
589594
590-         val  profileApps =  deferreds.flatMap { it.await() }
591-         allApps.addAll(profileApps)
595+         deferreds.forEach { rawApps.addAll(it.await()) }
592596
593597        //  🔹 Update profile counters
594598        listOf (" SYSTEM" " PRIVATE" " WORK" " USER" -> 
595-             val  count =  allApps.count { it.profileType ==  type }
596-             prefs.setProfileCounter(type, count)
599+             prefs.setProfileCounter(type, rawApps.count { it.profileType ==  type })
600+         }
601+ 
602+         //  🔹 Convert RawApp → AppListItem
603+         val  allApps =  rawApps.map { raw -> 
604+             AppListItem (
605+                 activityLabel =  raw.label,
606+                 activityPackage =  raw.pkg,
607+                 activityClass =  raw.cls,
608+                 user =  raw.user,
609+                 profileType =  raw.profileType,
610+                 customLabel =  prefs.getAppAlias(raw.pkg),
611+                 customTag =  prefs.getAppTag(raw.pkg, raw.user),
612+                 category =  raw.category
613+             )
597614        }
615+             //  🔹 Sort pinned apps first, then regular/recent alphabetically
616+             .sortedWith(
617+                 compareByDescending<AppListItem > { it.category ==  AppCategory .PINNED  }
618+                     .thenBy { normalizeForSort(it.label) }
619+             )
620+             .toMutableList()
598621
599-         //  🔹 Finalize list using buildList() 
622+         //  🔹 Build scroll map and finalize 
600623        buildList(
601624            items =  allApps,
602-             seenKey =  mutableSetOf (),  //  ✅ fresh set (don’t reuse seenAppKeys!) 
625+             seenKey =  mutableSetOf (),
603626            scrollMapLiveData =  _appScrollMap ,
604627            includeHidden =  includeHiddenApps,
605628            getKey =  { " ${it.activityPackage} |${it.activityClass} |${it.user.hashCode()} " 
606629            isHidden =  { it.activityPackage in  hiddenAppsSet },
607630            isPinned =  { it.activityPackage in  pinnedPackages },
608-             buildItem =  { it },  //  Already an AppListItem 
631+             buildItem =  { it },
609632            getLabel =  { it.label },
610633            normalize =  ::normalizeForSort
611634        )
0 commit comments