Introduce Lawnchair Advance Search (#3785)
* Introduce Lawnchair Device Search Initial Implementation device search in Lawnchair: Enables search functionality across Files, Contacts, and Suggestions on the Startpage. * Apply reviewed suggestions by @Gooler - Use retrofit for fetching suggestions * use kotlinxJson * Separate suggestion * Apply changes code refine @Goooler * Fix styling * Implement MIME Type Compat * Apply suggestion changes by @suphon-t * Apply suggestion changes by @SuperDragonXD * Cleanup * Remodel FileInfo * Private retrofit * Clean up SearchRequest * Add FileExtensions * Optimize file search method * Fix style * Rename * Optimize file opening * Add type checkers * Reformat AVDs * Fixed crash java.lang.ClassCastException error logs : https://katb.in/abahivihaqu * More fixes * Surround try catch to give proper error logs java.lang.NullPointerException at app.lawnchair.search.data.SearchRequestKt$findContactsByName$2.invokeSuspend(SearchRequest.kt:124) at app.lawnchair.search.data.SearchRequestKt$findContactsByName$2.invoke(Unknown Source:8) at app.lawnchair.search.data.SearchRequestKt$findContactsByName$2.invoke(Unknown Source:4) at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78) at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167) at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1) at app.lawnchair.search.data.SearchRequestKt.findContactsByName(SearchRequest.kt:68) at app.lawnchair.search.LawnchairSearchAlgorithm$performDeviceWideSearch$2.invokeSuspend(LawnchairSearchAlgorithm.kt:152) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) * Fixed opened file instead of just redirect to file manager * Fixed crash opening folder * Initial implementation callback * Add max results option for files, suggestion and people * Add initial settings entry * Properly use folder mimetype * Fixed target icon styling * Cleanups * Revert * Fixes * Apply suggestions by @Goooler * Fixed crash on external storage directory * Implement history keyword using built-in SearchRecentSuggestionsProvider * misc fixes * Add timeout 3 seconds - As we don't how long gonna take to receive response in startpage * Misc fixes * Misc fixes --------- Co-authored-by: Goooler <wangzongler@gmail.com>
This commit is contained in:
@@ -24,6 +24,11 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="com.google.android.apps.nexuslauncher.permission.QSB" />
|
||||
<uses-permission android:name="com.kieronquinn.app.smartspacer.permission.ACCESS_SMARTSPACER"/>
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_HISTORY_BOOKMARKS" />
|
||||
<uses-permission android:name="android.permission.WRITE_HISTORY_BOOKMARKS" />
|
||||
|
||||
|
||||
<permission
|
||||
android:name="${packageName}.permission.READ_SETTINGS"
|
||||
@@ -99,6 +104,12 @@
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="app.lawnchair.search.LawnchairRecentSuggestionProvider"
|
||||
android:authorities="app.lawnchair.search.LawnchairRecentSuggestionProvider"
|
||||
android:syncable="true"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="app.lawnchair.bugreport.BugReportReceiver"
|
||||
android:exported="false">
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,17H15M9,13H15M9,9H10M13,3H8.2C7.08,3 6.52,3 6.092,3.218C5.716,3.41 5.41,3.716 5.218,4.092C5,4.52 5,5.08 5,6.2V17.8C5,18.92 5,19.48 5.218,19.908C5.41,20.284 5.716,20.59 6.092,20.782C6.52,21 7.08,21 8.2,21H15.8C16.92,21 17.48,21 17.908,20.782C18.284,20.59 18.59,20.284 18.782,19.908C19,19.48 19,18.92 19,17.8V9M13,3L19,9M13,3V7.4C13,7.96 13,8.24 13.109,8.454C13.205,8.642 13.358,8.795 13.546,8.891C13.76,9 14.04,9 14.6,9H19"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,3V5.4C9,5.96 9,6.24 9.109,6.454C9.205,6.642 9.358,6.795 9.546,6.891C9.76,7 10.04,7 10.6,7H12.4C12.96,7 13.24,7 13.454,6.891C13.642,6.795 13.795,6.642 13.891,6.454C14,6.24 14,5.96 14,5.4V3.004M19,9.123V17.8C19,18.92 19,19.48 18.782,19.908C18.59,20.284 18.284,20.59 17.908,20.782C17.48,21 16.92,21 15.8,21H8.2C7.08,21 6.52,21 6.092,20.782C5.716,20.59 5.41,20.284 5.218,19.908C5,19.48 5,18.92 5,17.8V6.2C5,5.08 5,4.52 5.218,4.092C5.41,3.716 5.716,3.41 6.092,3.218C6.52,3 7.08,3 8.2,3H13.462C14.027,3 14.309,3 14.57,3.072C14.801,3.135 15.019,3.24 15.213,3.381C15.432,3.539 15.608,3.76 15.961,4.201L18.299,7.123C18.559,7.448 18.689,7.611 18.781,7.792C18.863,7.952 18.923,8.123 18.959,8.299C19,8.498 19,8.706 19,9.123ZM14,15.5C14,16.881 12.881,18 11.5,18C10.119,18 9,16.881 9,15.5C9,14.119 10.119,13 11.5,13C12.881,13 14,14.119 14,15.5Z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM11.26,14.793C10.87,15.183 10.87,15.817 11.26,16.207C11.651,16.598 12.284,16.598 12.674,16.207L11.26,14.793ZM9.062,12.251C8.924,12.786 9.246,13.331 9.781,13.468C10.316,13.606 10.861,13.284 10.999,12.749L9.062,12.251ZM11.967,17C11.415,17 10.967,17.448 10.967,18C10.967,18.552 11.415,19 11.967,19V17ZM11.977,19C12.529,19 12.977,18.552 12.977,18C12.977,17.448 12.529,17 11.977,17V19ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM12.967,13C12.967,13.173 12.922,13.305 12.648,13.57C12.494,13.718 12.313,13.865 12.062,14.074C11.824,14.272 11.544,14.509 11.26,14.793L12.674,16.207C12.89,15.991 13.111,15.803 13.341,15.612C13.559,15.431 13.815,15.222 14.037,15.009C14.513,14.549 14.967,13.931 14.967,13H12.967ZM11.967,12C12.519,12 12.967,12.448 12.967,13H14.967C14.967,11.343 13.624,10 11.967,10V12ZM10.999,12.749C11.11,12.318 11.503,12 11.967,12V10C10.568,10 9.395,10.957 9.062,12.251L10.999,12.749ZM11.967,19H11.977V17H11.967V19Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M14.264,15.938L12.596,14.283C11.791,13.485 11.388,13.086 10.927,12.94C10.52,12.812 10.084,12.816 9.68,12.954C9.222,13.109 8.828,13.517 8.041,14.333L4.044,18.28M14.264,15.938L14.605,15.599C15.411,14.8 15.814,14.4 16.277,14.254C16.683,14.126 17.12,14.131 17.524,14.269C17.982,14.425 18.376,14.834 19.163,15.651L20,16.493M14.264,15.938L18.275,19.956M18.275,19.956C17.918,20 17.454,20 16.8,20H7.2C6.08,20 5.52,20 5.092,19.782C4.716,19.59 4.41,19.284 4.218,18.908C4.128,18.731 4.075,18.532 4.044,18.28M18.275,19.956C18.529,19.926 18.73,19.873 18.908,19.782C19.284,19.59 19.59,19.284 19.782,18.908C20,18.48 20,17.92 20,16.8V16.493M4.044,18.28C4,17.922 4,17.458 4,16.8V7.2C4,6.08 4,5.52 4.218,5.092C4.41,4.716 4.716,4.41 5.092,4.218C5.52,4 6.08,4 7.2,4H16.8C17.92,4 18.48,4 18.908,4.218C19.284,4.41 19.59,4.716 19.782,5.092C20,5.52 20,6.08 20,7.2V16.493M17,9C17,10.104 16.105,11 15,11C13.895,11 13,10.104 13,9C13,7.895 13.895,7 15,7C16.105,7 17,7.895 17,9Z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12.5,8.89V18.5M12.5,8.89V5.577C12.5,5.369 12.5,5.266 12.535,5.177C12.565,5.099 12.615,5.03 12.679,4.977C12.752,4.916 12.85,4.883 13.047,4.818L17.447,3.351C17.802,3.232 17.98,3.173 18.122,3.209C18.246,3.24 18.353,3.317 18.422,3.425C18.5,3.548 18.5,3.735 18.5,4.11V7.423C18.5,7.631 18.5,7.734 18.465,7.823C18.435,7.9 18.385,7.969 18.321,8.023C18.248,8.084 18.15,8.117 17.953,8.182L13.553,9.649C13.198,9.767 13.02,9.827 12.878,9.791C12.754,9.76 12.647,9.683 12.578,9.575C12.5,9.452 12.5,9.265 12.5,8.89ZM12.5,18.5C12.5,19.881 10.933,21 9,21C7.067,21 5.5,19.881 5.5,18.5C5.5,17.119 7.067,16 9,16C10.933,16 12.5,17.119 12.5,18.5Z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM11.26,14.793C10.87,15.183 10.87,15.817 11.26,16.207C11.651,16.598 12.284,16.598 12.674,16.207L11.26,14.793ZM9.062,12.251C8.924,12.786 9.246,13.331 9.781,13.468C10.316,13.606 10.861,13.284 10.999,12.749L9.062,12.251ZM11.967,17C11.415,17 10.967,17.448 10.967,18C10.967,18.552 11.415,19 11.967,19V17ZM11.977,19C12.529,19 12.977,18.552 12.977,18C12.977,17.448 12.529,17 11.977,17V19ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM12.967,13C12.967,13.173 12.922,13.305 12.648,13.57C12.494,13.718 12.313,13.865 12.062,14.074C11.824,14.272 11.544,14.509 11.26,14.793L12.674,16.207C12.89,15.991 13.111,15.803 13.341,15.612C13.559,15.431 13.815,15.222 14.037,15.009C14.513,14.549 14.967,13.931 14.967,13H12.967ZM11.967,12C12.519,12 12.967,12.448 12.967,13H14.967C14.967,11.343 13.624,10 11.967,10V12ZM10.999,12.749C11.11,12.318 11.503,12 11.967,12V10C10.568,10 9.395,10.957 9.062,12.251L10.999,12.749ZM11.967,19H11.977V17H11.967V19Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM11.26,14.793C10.87,15.183 10.87,15.817 11.26,16.207C11.651,16.598 12.284,16.598 12.674,16.207L11.26,14.793ZM9.062,12.251C8.924,12.786 9.246,13.331 9.781,13.468C10.316,13.606 10.861,13.284 10.999,12.749L9.062,12.251ZM11.967,17C11.415,17 10.967,17.448 10.967,18C10.967,18.552 11.415,19 11.967,19V17ZM11.977,19C12.529,19 12.977,18.552 12.977,18C12.977,17.448 12.529,17 11.977,17V19ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM12.967,13C12.967,13.173 12.922,13.305 12.648,13.57C12.494,13.718 12.313,13.865 12.062,14.074C11.824,14.272 11.544,14.509 11.26,14.793L12.674,16.207C12.89,15.991 13.111,15.803 13.341,15.612C13.559,15.431 13.815,15.222 14.037,15.009C14.513,14.549 14.967,13.931 14.967,13H12.967ZM11.967,12C12.519,12 12.967,12.448 12.967,13H14.967C14.967,11.343 13.624,10 11.967,10V12ZM10.999,12.749C11.11,12.318 11.503,12 11.967,12V10C10.568,10 9.395,10.957 9.062,12.251L10.999,12.749ZM11.967,19H11.977V17H11.967V19Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM11.26,14.793C10.87,15.183 10.87,15.817 11.26,16.207C11.651,16.598 12.284,16.598 12.674,16.207L11.26,14.793ZM9.062,12.251C8.924,12.786 9.246,13.331 9.781,13.468C10.316,13.606 10.861,13.284 10.999,12.749L9.062,12.251ZM11.967,17C11.415,17 10.967,17.448 10.967,18C10.967,18.552 11.415,19 11.967,19V17ZM11.977,19C12.529,19 12.977,18.552 12.977,18C12.977,17.448 12.529,17 11.977,17V19ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM12.967,13C12.967,13.173 12.922,13.305 12.648,13.57C12.494,13.718 12.313,13.865 12.062,14.074C11.824,14.272 11.544,14.509 11.26,14.793L12.674,16.207C12.89,15.991 13.111,15.803 13.341,15.612C13.559,15.431 13.815,15.222 14.037,15.009C14.513,14.549 14.967,13.931 14.967,13H12.967ZM11.967,12C12.519,12 12.967,12.448 12.967,13H14.967C14.967,11.343 13.624,10 11.967,10V12ZM10.999,12.749C11.11,12.318 11.503,12 11.967,12V10C10.568,10 9.395,10.957 9.062,12.251L10.999,12.749ZM11.967,19H11.977V17H11.967V19Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,17H15M9,13H15M9,9H10M13,3H8.2C7.08,3 6.52,3 6.092,3.218C5.716,3.41 5.41,3.716 5.218,4.092C5,4.52 5,5.08 5,6.2V17.8C5,18.92 5,19.48 5.218,19.908C5.41,20.284 5.716,20.59 6.092,20.782C6.52,21 7.08,21 8.2,21H15.8C16.92,21 17.48,21 17.908,20.782C18.284,20.59 18.59,20.284 18.782,19.908C19,19.48 19,18.92 19,17.8V9M13,3L19,9M13,3V7.4C13,7.96 13,8.24 13.109,8.454C13.205,8.642 13.358,8.795 13.546,8.891C13.76,9 14.04,9 14.6,9H19"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,17H15M9,13H15M9,9H10M13,3H8.2C7.08,3 6.52,3 6.092,3.218C5.716,3.41 5.41,3.716 5.218,4.092C5,4.52 5,5.08 5,6.2V17.8C5,18.92 5,19.48 5.218,19.908C5.41,20.284 5.716,20.59 6.092,20.782C6.52,21 7.08,21 8.2,21H15.8C16.92,21 17.48,21 17.908,20.782C18.284,20.59 18.59,20.284 18.782,19.908C19,19.48 19,18.92 19,17.8V9M13,3L19,9M13,3V7.4C13,7.96 13,8.24 13.109,8.454C13.205,8.642 13.358,8.795 13.546,8.891C13.76,9 14.04,9 14.6,9H19"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3,9H9.5M21,9H9.5M9.5,9L14.5,4M14.5,4H17.8C18.847,4 19.404,4 19.822,4.178M14.5,4H6.2C5.08,4 4.52,4 4.092,4.218C3.716,4.41 3.41,4.716 3.218,5.092C3,5.52 3,6.08 3,7.2V16.8C3,17.92 3,18.48 3.218,18.908C3.41,19.284 3.716,19.59 4.092,19.782C4.52,20 5.08,20 6.2,20H17.8C18.92,20 19.48,20 19.908,19.782C20.284,19.59 20.59,19.284 20.782,18.908C21,18.48 21,17.92 21,16.8V7.2C21,6.08 21,5.52 20.782,5.092C20.59,4.716 20.284,4.41 19.908,4.218C19.88,4.204 19.851,4.19 19.822,4.178M9,4L4,9M15,9L19.822,4.178M15,14.5L10,17.5V11.5L15,14.5Z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM11.26,14.793C10.87,15.183 10.87,15.817 11.26,16.207C11.651,16.598 12.284,16.598 12.674,16.207L11.26,14.793ZM9.062,12.251C8.924,12.786 9.246,13.331 9.781,13.468C10.316,13.606 10.861,13.284 10.999,12.749L9.062,12.251ZM11.967,17C11.415,17 10.967,17.448 10.967,18C10.967,18.552 11.415,19 11.967,19V17ZM11.977,19C12.529,19 12.977,18.552 12.977,18C12.977,17.448 12.529,17 11.977,17V19ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM12.967,13C12.967,13.173 12.922,13.305 12.648,13.57C12.494,13.718 12.313,13.865 12.062,14.074C11.824,14.272 11.544,14.509 11.26,14.793L12.674,16.207C12.89,15.991 13.111,15.803 13.341,15.612C13.559,15.431 13.815,15.222 14.037,15.009C14.513,14.549 14.967,13.931 14.967,13H12.967ZM11.967,12C12.519,12 12.967,12.448 12.967,13H14.967C14.967,11.343 13.624,10 11.967,10V12ZM10.999,12.749C11.11,12.318 11.503,12 11.967,12V10C10.568,10 9.395,10.957 9.062,12.251L10.999,12.749ZM11.967,19H11.977V17H11.967V19Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13,3L13.707,2.293C13.52,2.105 13.265,2 13,2V3ZM19,9H20C20,8.735 19.895,8.48 19.707,8.293L19,9ZM13.109,8.454L14,8V8L13.109,8.454ZM13.546,8.891L14,8L13.546,8.891ZM10,13C10,12.448 9.552,12 9,12C8.448,12 8,12.448 8,13H10ZM8,16C8,16.552 8.448,17 9,17C9.552,17 10,16.552 10,16H8ZM8.5,9C7.948,9 7.5,9.448 7.5,10C7.5,10.552 7.948,11 8.5,11V9ZM9.5,11C10.052,11 10.5,10.552 10.5,10C10.5,9.448 10.052,9 9.5,9V11ZM8.5,6C7.948,6 7.5,6.448 7.5,7C7.5,7.552 7.948,8 8.5,8V6ZM9.5,8C10.052,8 10.5,7.552 10.5,7C10.5,6.448 10.052,6 9.5,6V8ZM17.908,20.782L17.454,19.891L17.454,19.891L17.908,20.782ZM18.782,19.908L19.673,20.362L18.782,19.908ZM5.218,19.908L4.327,20.362H4.327L5.218,19.908ZM6.092,20.782L6.546,19.891L6.546,19.891L6.092,20.782ZM6.092,3.218L5.638,2.327L5.638,2.327L6.092,3.218ZM5.218,4.092L4.327,3.638L4.327,3.638L5.218,4.092ZM12,3V7.4H14V3H12ZM14.6,10H19V8H14.6V10ZM12,7.4C12,7.664 11.999,7.921 12.017,8.138C12.036,8.367 12.08,8.637 12.218,8.908L14,8C14.029,8.058 14.019,8.08 14.01,7.975C14.001,7.859 14,7.697 14,7.4H12ZM14.6,8C14.304,8 14.141,7.999 14.025,7.99C13.92,7.981 13.943,7.971 14,8L13.092,9.782C13.363,9.92 13.633,9.964 13.862,9.983C14.079,10.001 14.337,10 14.6,10V8ZM12.218,8.908C12.41,9.284 12.716,9.59 13.092,9.782L14,8V8L12.218,8.908ZM8,13V16H10V13H8ZM8.5,11H9.5V9H8.5V11ZM8.5,8H9.5V6H8.5V8ZM13,2H8.2V4H13V2ZM4,6.2V17.8H6V6.2H4ZM8.2,22H15.8V20H8.2V22ZM20,17.8V9H18V17.8H20ZM19.707,8.293L13.707,2.293L12.293,3.707L18.293,9.707L19.707,8.293ZM15.8,22C16.344,22 16.811,22.001 17.195,21.969C17.59,21.937 17.984,21.866 18.362,21.673L17.454,19.891C17.404,19.916 17.304,19.954 17.032,19.976C16.749,19.999 16.377,20 15.8,20V22ZM18,17.8C18,18.377 17.999,18.749 17.976,19.032C17.954,19.304 17.916,19.404 17.891,19.454L19.673,20.362C19.866,19.984 19.937,19.59 19.969,19.195C20.001,18.811 20,18.344 20,17.8H18ZM18.362,21.673C18.927,21.385 19.385,20.927 19.673,20.362L17.891,19.454C17.795,19.642 17.642,19.795 17.454,19.891L18.362,21.673ZM4,17.8C4,18.344 3.999,18.811 4.031,19.195C4.063,19.59 4.134,19.984 4.327,20.362L6.109,19.454C6.084,19.404 6.046,19.304 6.024,19.032C6.001,18.749 6,18.377 6,17.8H4ZM8.2,20C7.623,20 7.251,19.999 6.968,19.976C6.696,19.954 6.595,19.916 6.546,19.891L5.638,21.673C6.016,21.866 6.41,21.937 6.805,21.969C7.189,22.001 7.656,22 8.2,22V20ZM4.327,20.362C4.615,20.927 5.074,21.385 5.638,21.673L6.546,19.891C6.358,19.795 6.205,19.642 6.109,19.454L4.327,20.362ZM8.2,2C7.656,2 7.189,1.999 6.805,2.031C6.41,2.063 6.016,2.134 5.638,2.327L6.546,4.109C6.595,4.084 6.696,4.046 6.968,4.024C7.251,4.001 7.623,4 8.2,4V2ZM6,6.2C6,5.623 6.001,5.251 6.024,4.968C6.046,4.696 6.084,4.595 6.109,4.546L4.327,3.638C4.134,4.016 4.063,4.41 4.031,4.805C3.999,5.189 4,5.656 4,6.2H6ZM5.638,2.327C5.074,2.615 4.615,3.074 4.327,3.638L6.109,4.546C6.205,4.358 6.358,4.205 6.546,4.109L5.638,2.327Z" />
|
||||
</vector>
|
||||
@@ -2,6 +2,7 @@
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M22,17C22,18.6569 20.6569,20 19,20H5C3.3431,20 2,18.6569 2,17V7C2,5.3432 3.3431,4 5,4H10L12,6H19C20.6569,6 22,7.3432 22,9V17ZM4,7V17C4,17.5523 4.4477,18 5,18H19C19.5523,18 20,17.5523 20,17V9C20,8.4477 19.5523,8 19,8H11L9,6H5C4.4477,6 4,6.4477 4,7Z"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M7,9H17M7,13H17M21,20L17.676,18.338C17.424,18.212 17.298,18.149 17.166,18.104C17.048,18.065 16.928,18.037 16.805,18.019C16.667,18 16.526,18 16.245,18H6.2C5.08,18 4.52,18 4.092,17.782C3.716,17.59 3.41,17.284 3.218,16.908C3,16.48 3,15.92 3,14.8V7.2C3,6.08 3,5.52 3.218,5.092C3.41,4.716 3.716,4.41 4.092,4.218C4.52,4 5.08,4 6.2,4H17.8C18.92,4 19.48,4 19.908,4.218C20.284,4.41 20.59,4.716 20.782,5.092C21,5.52 21,6.08 21,7.2V20Z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M18.604,12.727L15.565,9.087C15.257,8.691 15.103,8.493 15.023,8.271C14.951,8.074 14.922,7.865 14.936,7.656C14.952,7.421 15.045,7.188 15.231,6.722L15.942,4.945C16.218,4.256 16.356,3.911 16.593,3.685C16.803,3.486 17.066,3.352 17.35,3.299C17.672,3.238 18.032,3.328 18.752,3.508L20.72,4C20.72,14 13.72,21 3.72,21L3.228,19.032C3.048,18.312 2.958,17.952 3.019,17.63C3.072,17.346 3.206,17.083 3.405,16.873C3.631,16.636 3.976,16.498 4.665,16.222L6.251,15.588C6.782,15.375 7.048,15.269 7.313,15.261C7.547,15.254 7.779,15.301 7.992,15.4C8.232,15.512 8.434,15.714 8.839,16.119L11.925,19.157"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M4,12C4,16.418 7.582,20 12,20C16.418,20 20,16.418 20,12C20,7.582 16.418,4 12,4C9.611,4 7.466,5.048 6,6.708C5.916,6.804 5.834,6.901 5.755,7M12,8V12L14.5,14.5M5.754,4.004V7.004H8.754"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<app.lawnchair.allapps.SearchResultRightLeftIcon xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/search_result_icon_row_left_right"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/search_result_files_row_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp">
|
||||
|
||||
<app.lawnchair.util.ImageViewWrapper
|
||||
android:id="@+id/files_preview"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_files_preview_width"
|
||||
android:layout_height="@dimen/search_row_files_preview_height"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@+id/avatar"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/text_rows"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1.0"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingTop="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingEnd="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingBottom="@dimen/dynamic_grid_edge_margin"
|
||||
android:singleLine="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_title_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/delimiter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:text="•"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="4dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:paddingTop="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingEnd="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingBottom="@dimen/dynamic_grid_edge_margin"
|
||||
android:orientation="horizontal"
|
||||
android:singleLine="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon1"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon2"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_marginLeft="@dimen/search_result_padding_medium"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
</LinearLayout>
|
||||
</app.lawnchair.allapps.SearchResultRightLeftIcon>
|
||||
@@ -1,85 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<app.lawnchair.allapps.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="@dimen/search_result_row_height"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/search_result_margin"
|
||||
android:paddingTop="@dimen/search_result_padding"
|
||||
android:paddingBottom="@dimen/search_result_padding">
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@id/icon"
|
||||
android:layout_width="@dimen/search_row_icon_size"
|
||||
android:layout_height="wrap_content"
|
||||
launcher:iconDisplay="search_result_tall"
|
||||
launcher:layoutHorizontal="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/text_rows"
|
||||
android:layout_width="0.0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/search_result_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_title_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size" />
|
||||
</LinearLayout>
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@+id/shortcut_0"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_icon_size"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="@dimen/search_result_margin"
|
||||
android:textSize="@dimen/search_hero_inline_button_text_size"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true" />
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@+id/shortcut_1"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_icon_size"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/search_result_margin"
|
||||
android:textSize="@dimen/search_hero_inline_button_text_size"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true" />
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@+id/shortcut_2"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_icon_size"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/search_result_margin"
|
||||
android:textSize="@dimen/search_hero_inline_button_text_size"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true" />
|
||||
</app.lawnchair.allapps.SearchResultIconRow>
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<app.lawnchair.allapps.SearchResultRightLeftIcon xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/search_result_small_icon_row_left_right"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/search_result_small_row_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp">
|
||||
|
||||
<app.lawnchair.util.ImageViewWrapper
|
||||
android:id="@+id/files_preview"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_files_preview_width"
|
||||
android:layout_height="@dimen/search_row_files_preview_height"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<app.lawnchair.allapps.SearchResultIcon
|
||||
android:id="@+id/avatar"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/text_rows"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1.0"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingTop="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingEnd="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingBottom="@dimen/dynamic_grid_edge_margin"
|
||||
android:singleLine="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_title_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/delimiter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:text="•"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="4dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:paddingTop="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingEnd="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingBottom="@dimen/dynamic_grid_edge_margin"
|
||||
android:orientation="horizontal"
|
||||
android:singleLine="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon1"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon2"
|
||||
style="@style/BaseIcon"
|
||||
android:layout_marginLeft="@dimen/search_result_padding_medium"
|
||||
android:layout_width="@dimen/search_row_medium_icon_size"
|
||||
android:layout_height="@dimen/search_row_medium_icon_size"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
launcher:iconDisplay="search_result_small"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:matchTextInsetWithQuery="true" />
|
||||
|
||||
</LinearLayout>
|
||||
</app.lawnchair.allapps.SearchResultRightLeftIcon>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<app.lawnchair.allapps.SearchResultText xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/search_result_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/search_result_text_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/text_rows"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1.0"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingTop="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingEnd="@dimen/dynamic_grid_edge_margin"
|
||||
android:paddingBottom="@dimen/dynamic_grid_edge_margin"
|
||||
android:singleLine="true">
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/AllAppsSearchResult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/search_result_hero_subtitle_size" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
style="@android:style/Widget.DeviceDefault.Button.Borderless"
|
||||
android:id="@+id/clear_history"
|
||||
android:layout_width="wrap_content"
|
||||
android:textAllCaps="false"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/clear_history"
|
||||
android:visibility="gone"
|
||||
android:textStyle="normal"
|
||||
android:contentDescription="@string/recents_clear_all" />
|
||||
</app.lawnchair.allapps.SearchResultText>
|
||||
@@ -110,6 +110,7 @@
|
||||
<bool name="config_default_show_component_names">false</bool>
|
||||
<bool name="config_default_smartspace_show_date">true</bool>
|
||||
<bool name="config_default_smartspace_show_time">false</bool>
|
||||
<bool name="config_default_perform_wide_search">true</bool>
|
||||
|
||||
<item name="config_default_home_icon_size_factor" type="dimen" format="float">1.0</item>
|
||||
<item name="config_default_folder_preview_background_opacity" type="dimen" format="float">1.0</item>
|
||||
@@ -118,6 +119,12 @@
|
||||
<item name="config_default_drawer_icon_label_size_factor" type="dimen" format="float">1.0</item>
|
||||
<item name="config_default_drawer_cell_height_factor" type="dimen" format="float">1.0</item>
|
||||
<item name="config_default_search_max_result_count" type="dimen" format="integer">5</item>
|
||||
<item name="config_default_files_max_result_count" type="dimen" format="integer">3</item>
|
||||
<item name="config_default_people_max_result_count" type="dimen" format="integer">10</item>
|
||||
<item name="config_default_suggestion_max_result_count" type="dimen" format="integer">3</item>
|
||||
<item name="config_default_settings_entry_max_result_count" type="dimen" format="integer">5</item>
|
||||
<item name="config_default_recent_max_result_count" type="dimen" format="integer">2</item>
|
||||
|
||||
|
||||
<!-- The max scale for the wallpaper when it's zoomed in -->
|
||||
<item name="config_wallpaperMaxScale" format="float" type="dimen">1.10</item>
|
||||
|
||||
@@ -52,10 +52,13 @@
|
||||
<dimen name="search_result_hero_subtitle_size">14sp</dimen>
|
||||
<dimen name="search_result_margin">8dp</dimen>
|
||||
<dimen name="search_result_padding">16dp</dimen>
|
||||
<dimen name="search_result_padding_medium">12dp</dimen>
|
||||
<dimen name="search_result_radius">4dp</dimen>
|
||||
<dimen name="search_result_row_height">88dp</dimen>
|
||||
<dimen name="search_result_row_medium_height">72dp</dimen>
|
||||
<dimen name="search_result_small_row_height">56dp</dimen>
|
||||
<dimen name="search_result_files_row_height">96dp</dimen>
|
||||
<dimen name="search_result_text_height">52dp</dimen>
|
||||
<dimen name="search_result_subtitle_padding_start">4dp</dimen>
|
||||
|
||||
<!-- smartspace -->
|
||||
@@ -91,4 +94,13 @@
|
||||
<dimen name="status_bar_header_height_keyguard">40dp</dimen>
|
||||
|
||||
<dimen name="theme_icon_size">24dp</dimen>
|
||||
|
||||
<dimen name="search_row_medium_icon_size">24dp</dimen>
|
||||
<dimen name="search_row_mini_icon_size">22dp</dimen>
|
||||
<dimen name="space_layout_height">14dp</dimen>
|
||||
<dimen name="space_layout_mini_height">8dp</dimen>
|
||||
<dimen name="search_row_files_preview_width">80dp</dimen>
|
||||
<dimen name="search_row_files_preview_height">64dp</dimen>
|
||||
<dimen name="search_row_preview_radius">18dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -332,11 +332,25 @@
|
||||
<string name="fuzzy_search_title">Fuzzy Search</string>
|
||||
<string name="fuzzy_search_desc">Approximate matching for app searches.</string>
|
||||
<string name="max_search_result_count_title">Max Search Result Count</string>
|
||||
<string name="max_people_result_count_title">Max People Result Count</string>
|
||||
<string name="max_file_result_count_title">Max Files Result Count</string>
|
||||
<string name="max_suggestion_result_count_title">Max Suggestion Result Count</string>
|
||||
<string name="max_settings_entry_result_count_title">Max Settings Entry Result Count</string>
|
||||
<string name="max_recent_result_count_title">Max History Result Count</string>
|
||||
|
||||
<string name="clear_history">Clear</string>
|
||||
|
||||
|
||||
<!-- <string name="grid" /> -->
|
||||
<string name="app_drawer_columns">App Drawer Columns</string>
|
||||
<string name="row_height_label">Row Height</string>
|
||||
|
||||
<string name="perform_wide_search_title">Device Search</string>
|
||||
<string name="perform_wide_search_file">Files</string>
|
||||
<string name="pref_suggestion_title">Web suggestions (via <xliff:g id="startpage">Startpage</xliff:g>)</string>
|
||||
<string name="pref_advance_search_category">Advanced Search</string>
|
||||
<string name="perform_wide_search_description">Search your contacts, files, and settings in app drawer search.</string>
|
||||
<string name="pref_recent_suggestion_title">History</string>
|
||||
<!--
|
||||
<string name="icons" />
|
||||
<string name="icon_size" />
|
||||
@@ -481,9 +495,23 @@
|
||||
<string name="label_lens">Google Lens</string>
|
||||
<string name="label_voice_search">Voice Search</string>
|
||||
|
||||
<string name="search_pref_result_settings_entry_title">Settings Entry</string>
|
||||
|
||||
<string name="all_apps_search_market_message">Search for More Apps</string>
|
||||
<string name="all_apps_search_startpage_message">Search on <xliff:g id="startpage">Startpage</xliff:g></string>
|
||||
<string name="error_no_market_or_browser_installed">No app store or browser installed.</string>
|
||||
|
||||
<string name="all_apps_search_result_suggestions">From the web</string>
|
||||
<string name="all_apps_search_result_contacts_from_device">From your contacts</string>
|
||||
<string name="all_apps_search_result_files">From your files</string>
|
||||
<string name="all_apps_search_result_settings_entry_from_device">From your settings</string>
|
||||
|
||||
<string name="warn_contact_permission_content">Requires contact permission</string>
|
||||
<string name="warn_files_permission_content">Requires storage permission</string>
|
||||
|
||||
<string name="all_apps_search_result_files_description">Media, documents, folders, and more.</string>
|
||||
<string name="all_apps_search_result_contacts_description">Contacts and more</string>
|
||||
|
||||
<string name="quickstep_incompatible">Incompatible System Integration</string>
|
||||
<string name="quickstep_incompatible_description">Your device is configured to have system gestures (known as Quickstep) provided by %1$s, but this version of %1$s is not compatible with your Android version. To continue using your device, please uninstall %1$s updates or disable %1$s as a system-gesture provider.</string>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="log_files" path="logs"/>
|
||||
<external-path name="external_files" path="." />
|
||||
<external-path name="external_storage" path="/"/>
|
||||
</paths>
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.text.Selection
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned.SPAN_POINT_MARK
|
||||
@@ -23,6 +24,7 @@ import androidx.core.widget.addTextChangedListener
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import app.lawnchair.preferences2.PreferenceManager2
|
||||
import app.lawnchair.search.LawnchairRecentSuggestionProvider
|
||||
import app.lawnchair.search.LawnchairSearchAlgorithm
|
||||
import app.lawnchair.theme.drawable.DrawableTokens
|
||||
import com.android.launcher3.Insettable
|
||||
@@ -72,6 +74,8 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
private val bgAlphaAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 300 }
|
||||
private var bgVisible = true
|
||||
private var bgAlpha = 1f
|
||||
private val suggestionsRecent = SearchRecentSuggestions(launcher, LawnchairRecentSuggestionProvider.AUTHORITY, LawnchairRecentSuggestionProvider.MODE)
|
||||
private val pref = PreferenceManager.getInstance(launcher)
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
@@ -84,7 +88,7 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
input = ViewCompat.requireViewById(this, R.id.input)
|
||||
with(input) {
|
||||
if (LawnchairSearchAlgorithm.isDeviceSearchEnabled(context)) {
|
||||
if (pref.performWideSearchExperimental.get()) {
|
||||
setHint(R.string.all_apps_device_search_hint)
|
||||
} else {
|
||||
setHint(R.string.all_apps_search_bar_hint)
|
||||
@@ -102,6 +106,15 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
}
|
||||
|
||||
if (pref.searchResulRecentSuggestion.get()) {
|
||||
input.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
val query = editText.text.toString()
|
||||
suggestionsRecent.saveRecentQuery(query, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.addTextChangedListener(
|
||||
beforeTextChanged = { _, _, _, _ ->
|
||||
hint.isInvisible = true
|
||||
@@ -113,6 +126,13 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
enableDebugMenu.set(!enableDebugMenu.get())
|
||||
launcher.stateManager.goToState(LauncherState.NORMAL)
|
||||
}
|
||||
// Make sure to empty
|
||||
// if user used backspace instead of clear action btn
|
||||
if (input.text.isEmpty() || input.text.isBlank()) {
|
||||
input.reset()
|
||||
resetSearch()
|
||||
clearSearchResult()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -128,6 +148,10 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
updateHint()
|
||||
}
|
||||
|
||||
override fun refreshResults() {
|
||||
onAppsUpdated()
|
||||
}
|
||||
|
||||
private fun updateHint() {
|
||||
val inputString = input.text.toString()
|
||||
val inputLowerCase = inputString.lowercase(Locale.getDefault())
|
||||
@@ -207,16 +231,6 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
}
|
||||
|
||||
fun onAppendSearchResult(
|
||||
query: String,
|
||||
items: ArrayList<AdapterItem>?,
|
||||
) {
|
||||
if (items != null) {
|
||||
apps.setSearchResults(items)
|
||||
notifyResultChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearSearchResult() {
|
||||
if (apps.setSearchResults(null)) {
|
||||
notifyResultChanged()
|
||||
|
||||
@@ -18,13 +18,13 @@ class SearchItemBackground(
|
||||
private val resources = context.resources
|
||||
|
||||
private val searchDecorationPadding = resources.getDimensionPixelSize(R.dimen.search_decoration_padding)
|
||||
private val focusHighlight = ColorTokens.FocusHighlight.resolveColor(context)
|
||||
private val groupHighlight = if (showBackground) ColorTokens.GroupHighlight.resolveColor(context) else 0
|
||||
|
||||
private val cornerRadii: FloatArray
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val tmpPath = Path()
|
||||
private val tmpRect = RectF()
|
||||
val focusHighlight = ColorTokens.FocusHighlight.resolveColor(context)
|
||||
val groupHighlight = if (showBackground) ColorTokens.GroupHighlight.resolveColor(context) else 0
|
||||
|
||||
val cornerRadii: FloatArray
|
||||
|
||||
init {
|
||||
val searchGroupRadius = resources.getDimensionPixelSize(R.dimen.search_group_radius).toFloat()
|
||||
|
||||
@@ -11,7 +11,9 @@ import android.view.ViewGroup
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.search.SHORTCUT
|
||||
import app.lawnchair.search.SearchTargetCompat
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import app.lawnchair.util.runOnMainThread
|
||||
import com.android.launcher3.BubbleTextView
|
||||
import com.android.launcher3.LauncherAppState
|
||||
@@ -68,7 +70,7 @@ class SearchResultIcon(context: Context, attrs: AttributeSet?) :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>) {
|
||||
override fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>, callBack: SearchResultActionCallBack?) {
|
||||
if (boundId == target.id) return
|
||||
boundId = target.id
|
||||
flags = getFlags(target.extras)
|
||||
@@ -101,7 +103,7 @@ class SearchResultIcon(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
fun bind(target: SearchTargetCompat, callback: (info: ItemInfoWithIcon) -> Unit) {
|
||||
this.callback = callback
|
||||
bind(target, emptyList())
|
||||
bind(target, emptyList(), null)
|
||||
if (!hasFlag(flags, SearchResultView.FLAG_HIDE_ICON)) {
|
||||
isVisible = true
|
||||
val lp = layoutParams as ViewGroup.MarginLayoutParams
|
||||
@@ -184,7 +186,7 @@ class SearchResultIcon(context: Context, attrs: AttributeSet?) :
|
||||
notifyApplied(si)
|
||||
val cache = LauncherAppState.getInstance(launcher).iconCache
|
||||
MODEL_EXECUTOR.handler.postAtFrontOfQueue {
|
||||
cache.getShortcutInfoBadge(shortcutInfo)
|
||||
cache.getShortcutIcon(si, shortcutInfo)
|
||||
runOnMainThread { applyFromWorkspaceItem(si) }
|
||||
}
|
||||
}
|
||||
@@ -210,9 +212,9 @@ class SearchResultIcon(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
info.bitmap = when {
|
||||
info.hasFlags(FLAG_PRIMARY_ICON_FROM_TITLE) ->
|
||||
li.createIconBitmap("${info.title?.get(0)}", packageIcon.color)
|
||||
li.createIconBitmap("${info.title}", packageIcon.color)
|
||||
icon == null -> packageIcon
|
||||
else -> li.createBadgedIconBitmap(icon.loadDrawable(context), info.user, false)
|
||||
else -> li.createBadgedIconBitmap(icon.loadDrawable(context), info.user, target.packageName != SHORTCUT)
|
||||
}
|
||||
if (info.hasFlags(FLAG_BADGE_WITH_COMPONENT_NAME) && target.extras.containsKey("class")) {
|
||||
try {
|
||||
@@ -235,7 +237,7 @@ class SearchResultIcon(context: Context, attrs: AttributeSet?) :
|
||||
val las = LauncherAppState.getInstance(context)
|
||||
val info = PackageItemInfo(packageName, user)
|
||||
info.user = user
|
||||
las.iconCache.getTitleAndIconForApp(info, false)
|
||||
las.iconCache.getTitleAndIcon(info, false)
|
||||
return info.bitmap
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import app.lawnchair.allapps.SearchResultView.Companion.FLAG_HIDE_SUBTITLE
|
||||
import app.lawnchair.font.FontManager
|
||||
import app.lawnchair.search.RECENT_KEYWORD
|
||||
import app.lawnchair.search.SETTING
|
||||
import app.lawnchair.search.SUGGESTION
|
||||
import app.lawnchair.search.SearchTargetCompat
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import com.android.app.search.LayoutType
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.touch.ItemClickHandler
|
||||
@@ -41,6 +46,8 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) :
|
||||
title = ViewCompat.requireViewById(this, R.id.title)
|
||||
subtitle = ViewCompat.requireViewById(this, R.id.subtitle)
|
||||
subtitle.isVisible = false
|
||||
FontManager.INSTANCE.get(context).setCustomFont(title, R.id.font_heading)
|
||||
FontManager.INSTANCE.get(context).setCustomFont(subtitle, R.id.font_body)
|
||||
delimiter = findViewById(R.id.delimiter)
|
||||
setOnClickListener(icon)
|
||||
|
||||
@@ -68,7 +75,7 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>) {
|
||||
override fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>, callBack: SearchResultActionCallBack?) {
|
||||
if (boundId == target.id) return
|
||||
boundId = target.id
|
||||
flags = getFlags(target.extras)
|
||||
@@ -77,6 +84,14 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) :
|
||||
title.text = it.title
|
||||
tag = it
|
||||
}
|
||||
val isSuggestion = (target.layoutType == LayoutType.HORIZONTAL_MEDIUM_TEXT || target.layoutType == LayoutType.WIDGET_LIVE) &&
|
||||
target.resultType == SearchTargetCompat.RESULT_TYPE_SUGGESTIONS &&
|
||||
(target.packageName == SUGGESTION || target.packageName == RECENT_KEYWORD)
|
||||
|
||||
val isSetting = target.layoutType == LayoutType.ICON_SLICE &&
|
||||
target.resultType == SearchTargetCompat.RESULT_TYPE_SETTING_TILE &&
|
||||
target.packageName == SETTING
|
||||
|
||||
bindShortcuts(shortcuts)
|
||||
var showDelimiter = true
|
||||
if (isSmall) {
|
||||
@@ -94,6 +109,17 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
}
|
||||
setSubtitleText(target.searchAction?.subtitle, showDelimiter)
|
||||
if (shouldHandleClick(target) && !isSmall) {
|
||||
setOnClickListener {
|
||||
target.searchAction?.intent?.let { intent -> handleSearchTargetClick(context, intent) }
|
||||
}
|
||||
}
|
||||
if (isSuggestion || isSetting) {
|
||||
layoutParams.height = resources.getDimensionPixelSize(R.dimen.search_result_small_row_height)
|
||||
setOnClickListener {
|
||||
target.searchAction?.intent?.let { intent -> handleSearchTargetClick(context, intent) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSubtitleText(subtitleText: CharSequence?, showDelimiter: Boolean) {
|
||||
@@ -111,7 +137,7 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) :
|
||||
shortcutIcons.forEachIndexed { index, icon ->
|
||||
if (index < shortcuts.size) {
|
||||
icon.isVisible = true
|
||||
icon.bind(shortcuts[index], emptyList())
|
||||
icon.bind(shortcuts[index], emptyList(), null)
|
||||
} else {
|
||||
icon.isVisible = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package app.lawnchair.allapps
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import app.lawnchair.font.FontManager
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.search.SearchTargetCompat
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import app.lawnchair.util.AppInfo
|
||||
import app.lawnchair.util.AppInfoHelper
|
||||
import app.lawnchair.util.ImageViewWrapper
|
||||
import com.android.app.search.LayoutType
|
||||
import com.android.launcher3.R
|
||||
|
||||
class SearchResultRightLeftIcon(context: Context, attrs: AttributeSet?) :
|
||||
LinearLayout(context, attrs), SearchResultView {
|
||||
|
||||
private val launcher = context.launcher
|
||||
private lateinit var title: TextView
|
||||
private lateinit var avatar: SearchResultIcon
|
||||
private lateinit var call: ImageView
|
||||
private lateinit var message: ImageView
|
||||
private lateinit var preview: ImageViewWrapper
|
||||
private val appInfoHelper = AppInfoHelper(context)
|
||||
private var defPhoneAppInfo: AppInfo? = null
|
||||
private var defSmsAppInfo: AppInfo? = null
|
||||
private var isSmall = false
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
isSmall = id == R.id.search_result_small_icon_row_left_right
|
||||
defPhoneAppInfo = appInfoHelper.getDefaultPhoneAppInfo()
|
||||
defSmsAppInfo = appInfoHelper.getDefaultMessageAppInfo()
|
||||
onFocusChangeListener = launcher.focusHandler
|
||||
title = ViewCompat.requireViewById(this, R.id.title)
|
||||
avatar = ViewCompat.requireViewById(this, R.id.avatar)
|
||||
call = ViewCompat.requireViewById(this, R.id.icon2)
|
||||
message = ViewCompat.requireViewById(this, R.id.icon1)
|
||||
preview = ViewCompat.requireViewById(this, R.id.files_preview)
|
||||
FontManager.INSTANCE.get(context).setCustomFont(title, R.id.font_body)
|
||||
setUpdateResources()
|
||||
}
|
||||
|
||||
private fun setUpdateResources() {
|
||||
if (isSmall) {
|
||||
message.setImageDrawable(defSmsAppInfo?.appIcon)
|
||||
call.setImageDrawable(defPhoneAppInfo?.appIcon)
|
||||
call.visibility = VISIBLE
|
||||
message.visibility = VISIBLE
|
||||
avatar.visibility = VISIBLE
|
||||
preview.visibility = GONE
|
||||
} else {
|
||||
call.visibility = GONE
|
||||
message.visibility = GONE
|
||||
avatar.visibility = GONE
|
||||
preview.visibility = VISIBLE
|
||||
}
|
||||
val heightRes = if (isSmall) resources.getDimensionPixelSize(R.dimen.search_result_small_row_height) else resources.getDimensionPixelSize(R.dimen.search_result_files_row_height)
|
||||
val layoutParams = LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
heightRes,
|
||||
)
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
override val isQuickLaunch: Boolean get() = false
|
||||
|
||||
override val titleText: CharSequence? get() = title.text
|
||||
|
||||
override fun launch(): Boolean = false
|
||||
|
||||
override fun bind(
|
||||
target: SearchTargetCompat,
|
||||
shortcuts: List<SearchTargetCompat>,
|
||||
callBack: SearchResultActionCallBack?,
|
||||
) {
|
||||
title.text = target.searchAction?.title
|
||||
val isNewFile = target.resultType == SearchTargetCompat.RESULT_TYPE_FILE_TILE &&
|
||||
target.layoutType == LayoutType.THUMBNAIL
|
||||
val isFile = !isSmall && isNewFile
|
||||
|
||||
if (!isFile) {
|
||||
avatar.bind(target) {
|
||||
title.text = it.title
|
||||
tag = it
|
||||
}
|
||||
val number = target.searchAction?.subtitle.toString()
|
||||
message.setOnClickListener {
|
||||
defSmsAppInfo?.let { appInfo ->
|
||||
launchApp(appInfo.packageName, number)
|
||||
}
|
||||
}
|
||||
call.setOnClickListener {
|
||||
defPhoneAppInfo?.let { appInfo ->
|
||||
launchApp(appInfo.packageName, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFile) {
|
||||
isSmall = true
|
||||
setUpdateResources()
|
||||
}
|
||||
|
||||
if (isFile) {
|
||||
preview.setImageIcon(target.searchAction?.icon)
|
||||
title.isSingleLine = false
|
||||
}
|
||||
|
||||
if (shouldHandleClick(target)) {
|
||||
setOnClickListener {
|
||||
target.searchAction?.intent?.let { intent -> handleSearchTargetClick(context, intent) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(packageName: String, phoneNumber: String? = null) {
|
||||
val packageManager = context.packageManager
|
||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
|
||||
launchIntent ?: return
|
||||
if (packageName == defSmsAppInfo?.packageName) {
|
||||
val smsIntent = Intent(Intent.ACTION_VIEW)
|
||||
smsIntent.data = Uri.parse("smsto:$phoneNumber")
|
||||
smsIntent.putExtra("address", phoneNumber)
|
||||
handleSearchTargetClick(context, smsIntent)
|
||||
} else if (packageName == defPhoneAppInfo?.packageName && phoneNumber != null) {
|
||||
val phoneIntent = Intent(Intent.ACTION_DIAL)
|
||||
phoneIntent.data = Uri.parse("tel:$phoneNumber")
|
||||
handleSearchTargetClick(context, phoneIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package app.lawnchair.allapps
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import app.lawnchair.font.FontManager
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.search.HEADER_JUSTIFY
|
||||
import app.lawnchair.search.LawnchairRecentSuggestionProvider
|
||||
import app.lawnchair.search.SPACE
|
||||
import app.lawnchair.search.SPACE_MINI
|
||||
import app.lawnchair.search.SearchTargetCompat
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import app.lawnchair.theme.color.ColorTokens
|
||||
import com.android.launcher3.R
|
||||
|
||||
class SearchResultText(context: Context, attrs: AttributeSet?) :
|
||||
LinearLayout(context, attrs), SearchResultView {
|
||||
|
||||
private val launcher = context.launcher
|
||||
private lateinit var title: TextView
|
||||
private lateinit var clearHistory: Button
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
onFocusChangeListener = launcher.focusHandler
|
||||
title = ViewCompat.requireViewById(this, R.id.title)
|
||||
title.setTextColor(ColorTokens.ColorAccent.resolveColor(context))
|
||||
clearHistory = ViewCompat.requireViewById(this, R.id.clear_history)
|
||||
clearHistory.setTextColor(ColorTokens.ColorAccent.resolveColor(context))
|
||||
clearHistory.visibility = GONE
|
||||
FontManager.INSTANCE.get(context).setCustomFont(title, R.id.font_heading)
|
||||
FontManager.INSTANCE.get(context).setCustomFont(clearHistory, R.id.font_heading)
|
||||
}
|
||||
|
||||
override val isQuickLaunch: Boolean get() = false
|
||||
override val titleText: CharSequence? get() = title.text
|
||||
override fun launch(): Boolean = false
|
||||
|
||||
override fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>, callBack: SearchResultActionCallBack?) {
|
||||
title.text = target.searchAction?.title
|
||||
val res = when (title.text) {
|
||||
SPACE -> resources.getDimensionPixelSize(R.dimen.space_layout_height)
|
||||
SPACE_MINI -> resources.getDimensionPixelSize(R.dimen.space_layout_mini_height)
|
||||
else -> resources.getDimensionPixelSize(R.dimen.search_result_text_height)
|
||||
}
|
||||
clearHistory.visibility = if (target.packageName == HEADER_JUSTIFY) VISIBLE else INVISIBLE
|
||||
if (target.packageName == HEADER_JUSTIFY) {
|
||||
clearHistory.setOnClickListener {
|
||||
val suggestionsRecent = SearchRecentSuggestions(launcher, LawnchairRecentSuggestionProvider.AUTHORITY, LawnchairRecentSuggestionProvider.MODE)
|
||||
suggestionsRecent.clearHistory()
|
||||
callBack?.action()
|
||||
}
|
||||
}
|
||||
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, res)
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
package app.lawnchair.allapps
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import app.lawnchair.search.CONTACT
|
||||
import app.lawnchair.search.FILES
|
||||
import app.lawnchair.search.MARKET_STORE
|
||||
import app.lawnchair.search.START_PAGE
|
||||
import app.lawnchair.search.SUGGESTION
|
||||
import app.lawnchair.search.SearchTargetCompat
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import com.android.app.search.LayoutType
|
||||
|
||||
sealed interface SearchResultView {
|
||||
|
||||
@@ -10,7 +20,7 @@ sealed interface SearchResultView {
|
||||
|
||||
fun launch(): Boolean
|
||||
|
||||
fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>)
|
||||
fun bind(target: SearchTargetCompat, shortcuts: List<SearchTargetCompat>, callBack: SearchResultActionCallBack?)
|
||||
|
||||
fun getFlags(extras: Bundle): Int {
|
||||
var flags = 0
|
||||
@@ -30,6 +40,21 @@ sealed interface SearchResultView {
|
||||
return (flags and flag) != 0
|
||||
}
|
||||
|
||||
fun shouldHandleClick(targetCompat: SearchTargetCompat): Boolean {
|
||||
val packageName = targetCompat.packageName
|
||||
return (packageName in listOf(START_PAGE, MARKET_STORE, SUGGESTION, CONTACT, FILES)) &&
|
||||
targetCompat.layoutType != LayoutType.SMALL_ICON_HORIZONTAL_TEXT &&
|
||||
targetCompat.resultType != SearchTargetCompat.RESULT_TYPE_SHORTCUT
|
||||
}
|
||||
|
||||
fun handleSearchTargetClick(context: Context, searchTargetIntent: Intent) {
|
||||
searchTargetIntent.resolveActivity(context.packageManager)?.let {
|
||||
context.startActivity(searchTargetIntent)
|
||||
} ?: run {
|
||||
Toast.makeText(context, "No app found to handle this action", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FLAG_HIDE_SUBTITLE = 1 shl 0
|
||||
const val FLAG_HIDE_ICON = 1 shl 1
|
||||
|
||||
@@ -74,10 +74,16 @@ class PreferenceManager private constructor(private val context: Context) : Base
|
||||
|
||||
val deviceSearch = BoolPref("device_search", true, recreate)
|
||||
val searchResultShortcuts = BoolPref("pref_searchResultShortcuts", true)
|
||||
val searchResultPeople = BoolPref("pref_searchResultPeople", true)
|
||||
val searchResultPeople = BoolPref("pref_searchResultPeople", false, recreate)
|
||||
val searchResultPixelTips = BoolPref("pref_searchResultPixelTips", false)
|
||||
val searchResultSettings = BoolPref("pref_searchResultSettings", false)
|
||||
|
||||
val searchResultFiles = BoolPref("pref_searchResultFiles", false, recreate)
|
||||
val searchResultStartPageSuggestion = BoolPref("pref_searchResultStartPageSuggestion", true, recreate)
|
||||
val performWideSearchExperimental = BoolPref("pref_searchWideExperimental", false, recreate)
|
||||
val searchResultSettingsEntry = BoolPref("pref_searchResultSettingsEntry", false, recreate)
|
||||
val searchResulRecentSuggestion = BoolPref("pref_searchResultRecentSuggestion", false, recreate)
|
||||
|
||||
val themedIcons = BoolPref("themed_icons", true, recreate)
|
||||
val drawerThemedIcons = BoolPref("drawer_themed_icons", false, recreate)
|
||||
val hotseatQsbCornerRadius = FloatPref("pref_hotseatQsbCornerRadius", 1F, recreate)
|
||||
|
||||
@@ -254,6 +254,11 @@ class PreferenceManager2 private constructor(private val context: Context) : Pre
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_auto_show_keyboard_in_drawer),
|
||||
)
|
||||
|
||||
val performWideSearch = preference(
|
||||
key = booleanPreferencesKey(name = "performWideSearch"),
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_perform_wide_search),
|
||||
)
|
||||
|
||||
val workspaceTextColor = preference(
|
||||
key = stringPreferencesKey(name = "workspace_text_color"),
|
||||
defaultValue = ColorMode.AUTO,
|
||||
@@ -320,6 +325,31 @@ class PreferenceManager2 private constructor(private val context: Context) : Pre
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_search_max_result_count),
|
||||
)
|
||||
|
||||
val maxSuggestionResultCount = preference(
|
||||
key = intPreferencesKey(name = "max_suggestion_result_count"),
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_suggestion_max_result_count),
|
||||
)
|
||||
|
||||
val maxFileResultCount = preference(
|
||||
key = intPreferencesKey(name = "max_files_result_count"),
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_files_max_result_count),
|
||||
)
|
||||
|
||||
val maxPeopleResultCount = preference(
|
||||
key = intPreferencesKey(name = "max_people_result_count"),
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_people_max_result_count),
|
||||
)
|
||||
|
||||
val maxSettingsEntryResultCount = preference(
|
||||
key = intPreferencesKey(name = "max_settings_entry_result_count"),
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_settings_entry_max_result_count),
|
||||
)
|
||||
|
||||
val maxRecentResultCount = preference(
|
||||
key = intPreferencesKey(name = "max_recent_result_count"),
|
||||
defaultValue = resourceProvider.getInt(R.dimen.config_default_recent_max_result_count),
|
||||
)
|
||||
|
||||
val enableSmartspace = preference(
|
||||
key = booleanPreferencesKey(name = "enable_smartspace"),
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_enable_smartspace),
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
package app.lawnchair.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.core.os.bundleOf
|
||||
import app.lawnchair.allapps.SearchResultView
|
||||
import app.lawnchair.search.data.ContactInfo
|
||||
import app.lawnchair.search.data.FileInfo
|
||||
import app.lawnchair.search.data.FileInfo.Companion.isImageType
|
||||
import app.lawnchair.search.data.FolderInfo
|
||||
import app.lawnchair.search.data.IFileInfo
|
||||
import app.lawnchair.search.data.RecentKeyword
|
||||
import app.lawnchair.search.data.SettingInfo
|
||||
import app.lawnchair.theme.color.ColorTokens
|
||||
import app.lawnchair.util.createTextBitmap
|
||||
import app.lawnchair.util.file2Uri
|
||||
import app.lawnchair.util.mimeCompat
|
||||
import com.android.app.search.LayoutType
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.util.ComponentKey
|
||||
import com.android.launcher3.util.PackageManagerHelper
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.Locale
|
||||
|
||||
class GenerateSearchTarget(private val context: Context) {
|
||||
|
||||
private val marketSearchComponent = resolveMarketSearchActivity()
|
||||
|
||||
internal fun getSuggestionTarget(suggestion: String): SearchTargetCompat {
|
||||
val url = getStartPageUrl(suggestion)
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val id = suggestion + url
|
||||
val action = SearchActionCompat.Builder(id, suggestion)
|
||||
.setIcon(
|
||||
Icon.createWithResource(context, R.drawable.ic_allapps_search)
|
||||
.setTint(ColorTokens.Accent1_600.resolveColor(context)),
|
||||
)
|
||||
.setIntent(browserIntent)
|
||||
.build()
|
||||
return createSearchTarget(
|
||||
id,
|
||||
action,
|
||||
LayoutType.HORIZONTAL_MEDIUM_TEXT,
|
||||
SearchTargetCompat.RESULT_TYPE_SUGGESTIONS,
|
||||
SUGGESTION,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getRecentKeywordTarget(recentKeyword: RecentKeyword): SearchTargetCompat {
|
||||
val value = recentKeyword.getValueByKey("display1") ?: ""
|
||||
val url = getStartPageUrl(value)
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val id = recentKeyword.data.toString() + url
|
||||
val action = SearchActionCompat.Builder(id, value)
|
||||
.setIcon(
|
||||
Icon.createWithResource(context, R.drawable.ic_recent)
|
||||
.setTint(ColorTokens.TextColorSecondary.resolveColor(context)),
|
||||
)
|
||||
.setIntent(browserIntent)
|
||||
.build()
|
||||
return createSearchTarget(
|
||||
id,
|
||||
action,
|
||||
LayoutType.WIDGET_LIVE,
|
||||
SearchTargetCompat.RESULT_TYPE_SUGGESTIONS,
|
||||
RECENT_KEYWORD,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getHeaderTarget(header: String, pkg: String = HEADER): SearchTargetCompat {
|
||||
val id = "header_$header"
|
||||
val action = SearchActionCompat.Builder(id, header)
|
||||
.setIcon(
|
||||
Icon.createWithResource(context, R.drawable.ic_allapps_search)
|
||||
.setTint(ColorTokens.TextColorPrimary.resolveColor(context)),
|
||||
)
|
||||
.setIntent(Intent())
|
||||
.build()
|
||||
return createSearchTarget(
|
||||
id,
|
||||
action,
|
||||
LayoutType.TEXT_HEADER,
|
||||
SearchTargetCompat.RESULT_TYPE_SECTION_HEADER,
|
||||
pkg,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getSettingSearchItem(info: SettingInfo): SearchTargetCompat? {
|
||||
val id = "_${info.id}"
|
||||
|
||||
val intent = Intent(info.action)
|
||||
if (info.requiresUri) {
|
||||
intent.data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
|
||||
if (intent.resolveActivity(context.packageManager) == null) {
|
||||
Log.w("SettingSearch", "No activity found to handle intent: $intent")
|
||||
return null
|
||||
}
|
||||
|
||||
if (!hasRequiredPermissions(intent)) {
|
||||
Log.w("SettingSearch", "App does not have required permissions for intent: $intent")
|
||||
return null
|
||||
}
|
||||
|
||||
val actionBuilder = SearchActionCompat.Builder(id, formatSettingTitle(info.name))
|
||||
.setIcon(
|
||||
Icon.createWithResource(context, R.drawable.ic_setting)
|
||||
.setTint(ColorTokens.Accent1_600.resolveColor(context)),
|
||||
)
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
|
||||
return createSearchTarget(
|
||||
id,
|
||||
actionBuilder,
|
||||
LayoutType.ICON_SLICE,
|
||||
SearchTargetCompat.RESULT_TYPE_SETTING_TILE,
|
||||
SETTING,
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasRequiredPermissions(intent: Intent): Boolean {
|
||||
val packageManager = context.packageManager
|
||||
val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
return resolveInfo?.activityInfo?.exported == true
|
||||
}
|
||||
|
||||
private fun formatSettingTitle(rawTitle: String?): String {
|
||||
return rawTitle?.replace('_', ' ')
|
||||
?.replace("ACTION", "")
|
||||
?.lowercase()
|
||||
?.split(' ')
|
||||
?.joinToString(" ") {
|
||||
it.replaceFirstChar { char -> char.uppercase(Locale.ROOT) }
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
internal fun getMarketSearchItem(query: String): SearchTargetCompat? {
|
||||
if (marketSearchComponent == null) return null
|
||||
val id = "marketSearch:$query"
|
||||
val action = SearchActionCompat.Builder(id, context.getString(R.string.all_apps_search_market_message))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_launcher_home))
|
||||
.setIntent(PackageManagerHelper.getMarketSearchIntent(context, query))
|
||||
.build()
|
||||
val extras = bundleOf(
|
||||
SearchResultView.EXTRA_ICON_COMPONENT_KEY to marketSearchComponent.toString(),
|
||||
SearchResultView.EXTRA_HIDE_SUBTITLE to true,
|
||||
)
|
||||
return createSearchTarget(id, action, MARKET_STORE, extras)
|
||||
}
|
||||
|
||||
internal fun getStartPageSearchItem(query: String): SearchTargetCompat {
|
||||
val url = getStartPageUrl(query)
|
||||
val id = "browser:$query"
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val action = SearchActionCompat.Builder(id, context.getString(R.string.all_apps_search_startpage_message))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_startpage))
|
||||
.setIntent(browserIntent)
|
||||
.build()
|
||||
val extras = bundleOf(
|
||||
SearchResultView.EXTRA_HIDE_SUBTITLE to true,
|
||||
)
|
||||
return createSearchTarget(id, action, START_PAGE, extras)
|
||||
}
|
||||
|
||||
internal fun getContactSearchItem(info: ContactInfo): SearchTargetCompat {
|
||||
val id = "contact:${info.contactId}${info.number}"
|
||||
val contactUri = Uri.withAppendedPath(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
info.contactId,
|
||||
)
|
||||
|
||||
val contactIntent = Intent(Intent.ACTION_VIEW, contactUri)
|
||||
val action = SearchActionCompat.Builder(id, info.name)
|
||||
.setIcon(displayContactPhoto(context, info.name, Uri.parse(info.uri)))
|
||||
.setContentDescription(info.contactId)
|
||||
.setSubtitle(info.number)
|
||||
.setIntent(contactIntent)
|
||||
.build()
|
||||
|
||||
return createSearchTarget(
|
||||
id,
|
||||
action,
|
||||
LayoutType.PEOPLE_TILE,
|
||||
SearchTargetCompat.RESULT_TYPE_CONTACT_TILE,
|
||||
CONTACT,
|
||||
Bundle(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getFileInfoSearchItem(info: IFileInfo): SearchTargetCompat {
|
||||
val fileUri = when (info) {
|
||||
is FileInfo -> Uri.withAppendedPath(
|
||||
MediaStore.Files.getContentUri("external"),
|
||||
info.fileId,
|
||||
)
|
||||
is FolderInfo -> File(info.path).file2Uri()
|
||||
}
|
||||
|
||||
val mimeType = when (info) {
|
||||
is FileInfo -> info.mimeType.mimeCompat
|
||||
is FolderInfo -> "resource/folder"
|
||||
}
|
||||
|
||||
val fileIntent = Intent(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
.setDataAndType(fileUri, mimeType)
|
||||
|
||||
val action = SearchActionCompat.Builder(info.path, info.name)
|
||||
.setIcon(getPreviewIcon(info))
|
||||
.setIntent(fileIntent)
|
||||
.build()
|
||||
|
||||
return createSearchTarget(
|
||||
info.path,
|
||||
action,
|
||||
LayoutType.THUMBNAIL,
|
||||
SearchTargetCompat.RESULT_TYPE_FILE_TILE,
|
||||
FILES,
|
||||
Bundle(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun displayContactPhoto(context: Context, name: String, contactUri: Uri): Icon {
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
inputStream = context.contentResolver.openInputStream(contactUri)
|
||||
inputStream?.use { input ->
|
||||
val bitmap = BitmapFactory.decodeStream(input)
|
||||
return Icon.createWithAdaptiveBitmap(bitmap)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// ignore
|
||||
} finally {
|
||||
inputStream?.close()
|
||||
}
|
||||
|
||||
// If contact photo not available, create an icon with the first letter of the name
|
||||
val initial = if (name.isNotEmpty()) name[0].uppercaseChar().toString() else "U"
|
||||
val textBitmap = createTextBitmap(context, initial)
|
||||
return Icon.createWithBitmap(textBitmap)
|
||||
}
|
||||
|
||||
private fun getStartPageUrl(query: String): String {
|
||||
return "https://www.startpage.com/do/search?segment=startpage.lawnchair&query=$query&cat=web"
|
||||
}
|
||||
|
||||
private fun resolveMarketSearchActivity(): ComponentKey? {
|
||||
val intent = PackageManagerHelper.getMarketSearchIntent(context, "")
|
||||
val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return null
|
||||
return ComponentKey(launchIntent.component, Process.myUserHandle())
|
||||
}
|
||||
|
||||
private fun getPreviewIcon(info: IFileInfo): Icon {
|
||||
val fileInfo = info as? FileInfo
|
||||
return if (fileInfo?.isImageType == true) {
|
||||
Icon.createWithFilePath(fileInfo.path)
|
||||
} else {
|
||||
Icon.createWithResource(context, fileInfo?.iconRes ?: R.drawable.ic_folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,18 @@ package app.lawnchair.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Handler
|
||||
import android.os.Process
|
||||
import androidx.core.os.bundleOf
|
||||
import app.lawnchair.allapps.SearchResultView
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.preferences2.PreferenceManager2
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import app.lawnchair.search.data.ContactInfo
|
||||
import app.lawnchair.search.data.IFileInfo
|
||||
import app.lawnchair.search.data.RecentKeyword
|
||||
import app.lawnchair.search.data.SearchResult
|
||||
import app.lawnchair.search.data.SettingInfo
|
||||
import app.lawnchair.util.isDefaultLauncher
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH
|
||||
import com.android.launcher3.model.AllAppsList
|
||||
import com.android.launcher3.model.BaseModelUpdateTask
|
||||
import com.android.launcher3.model.BgDataModel
|
||||
@@ -21,13 +22,11 @@ import com.android.launcher3.popup.PopupPopulator
|
||||
import com.android.launcher3.search.SearchCallback
|
||||
import com.android.launcher3.search.StringMatcherUtility
|
||||
import com.android.launcher3.shortcuts.ShortcutRequest
|
||||
import com.android.launcher3.util.ComponentKey
|
||||
import com.android.launcher3.util.Executors
|
||||
import com.android.launcher3.util.PackageManagerHelper
|
||||
import com.patrykmichalik.opto.core.onEach
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import me.xdrop.fuzzywuzzy.algorithms.WeightedRatio
|
||||
|
||||
@@ -40,9 +39,10 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
|
||||
private lateinit var hiddenApps: Set<String>
|
||||
private var showHiddenAppsInSearch = false
|
||||
private var enableSmartHide = false
|
||||
private val marketSearchComponent = resolveMarketSearchActivity()
|
||||
private val coroutineScope = CoroutineScope(context = Dispatchers.IO)
|
||||
private val pref2 = PreferenceManager2.getInstance(context)
|
||||
private val generateSearchTarget = GenerateSearchTarget(context)
|
||||
private var enableWideSearch = false
|
||||
|
||||
private val prefs: PreferenceManager = PreferenceManager.getInstance(context)
|
||||
|
||||
init {
|
||||
pref2.enableFuzzySearch.onEach(launchIn = coroutineScope) {
|
||||
@@ -60,13 +60,18 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
|
||||
pref2.enableSmartHide.onEach(launchIn = coroutineScope) {
|
||||
enableSmartHide = it
|
||||
}
|
||||
pref2.performWideSearch.onEach(launchIn = coroutineScope) {
|
||||
enableWideSearch = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun doSearch(query: String, callback: SearchCallback<AdapterItem>) {
|
||||
appState.model.enqueueModelUpdateTask(object : BaseModelUpdateTask() {
|
||||
override fun execute(app: LauncherAppState, dataModel: BgDataModel, apps: AllAppsList) {
|
||||
val result = getResult(apps.data, query)
|
||||
resultHandler.post { callback.onSearchResult(query, result) }
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
val results = getResult(apps.data, query)
|
||||
callback.onSearchResult(query, results)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -77,35 +82,82 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResult(
|
||||
private suspend fun getResult(
|
||||
apps: MutableList<AppInfo>,
|
||||
query: String,
|
||||
): java.util.ArrayList<AdapterItem> {
|
||||
): ArrayList<AdapterItem> {
|
||||
val appResults = if (enableFuzzySearch) {
|
||||
fuzzySearch(apps, query)
|
||||
} else {
|
||||
normalSearch(apps, query)
|
||||
}
|
||||
val results = mutableListOf<SearchTargetCompat>()
|
||||
if (appResults.size == 1) {
|
||||
val app = appResults.first()
|
||||
val shortcuts = getShortcuts(app)
|
||||
results.add(createSearchTarget(app, true))
|
||||
shortcuts.mapTo(results, ::createSearchTarget)
|
||||
} else {
|
||||
appResults.mapTo(results, ::createSearchTarget)
|
||||
|
||||
val wideSearchResults = if (enableWideSearch) performDeviceWideSearch(query, prefs) else emptyList()
|
||||
|
||||
val searchTargets = mutableListOf<SearchTargetCompat>()
|
||||
|
||||
if (appResults.isNotEmpty()) {
|
||||
appResults.mapTo(searchTargets, ::createSearchTarget)
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
if (marketSearchComponent == null) {
|
||||
return arrayListOf(AdapterItem(VIEW_TYPE_EMPTY_SEARCH))
|
||||
|
||||
if (appResults.size == 1 && context.isDefaultLauncher()) {
|
||||
val singleAppResult = appResults.first()
|
||||
val shortcuts = getShortcuts(singleAppResult)
|
||||
if (shortcuts.isNotEmpty()) {
|
||||
searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE))
|
||||
searchTargets.add(createSearchTarget(singleAppResult, true))
|
||||
searchTargets.addAll(shortcuts.map(::createSearchTarget))
|
||||
}
|
||||
results.add(getEmptySearchItem(query))
|
||||
}
|
||||
val adapterItems = transformSearchResults(results)
|
||||
|
||||
val contacts = filterByType(wideSearchResults, CONTACT)
|
||||
if (contacts.isNotEmpty()) {
|
||||
val contactsHeader = generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_contacts_from_device))
|
||||
searchTargets.add(contactsHeader)
|
||||
searchTargets.addAll(contacts.map { generateSearchTarget.getContactSearchItem(it.resultData as ContactInfo) })
|
||||
}
|
||||
|
||||
val settings = filterByType(wideSearchResults, SETTING)
|
||||
if (settings.isNotEmpty()) {
|
||||
val settingsHeader = generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_settings_entry_from_device))
|
||||
searchTargets.add(settingsHeader)
|
||||
searchTargets.addAll(settings.mapNotNull { generateSearchTarget.getSettingSearchItem(it.resultData as SettingInfo) })
|
||||
}
|
||||
|
||||
val recentKeyword = filterByType(wideSearchResults, RECENT_KEYWORD)
|
||||
if (recentKeyword.isNotEmpty()) {
|
||||
val recentKeywordHeader = generateSearchTarget.getHeaderTarget(context.getString(R.string.pref_recent_suggestion_title), HEADER_JUSTIFY)
|
||||
searchTargets.add(recentKeywordHeader)
|
||||
searchTargets.addAll(recentKeyword.map { generateSearchTarget.getRecentKeywordTarget(it.resultData as RecentKeyword) })
|
||||
}
|
||||
|
||||
val suggestions = filterByType(wideSearchResults, SUGGESTION)
|
||||
if (suggestions.isNotEmpty()) {
|
||||
val suggestionsHeader = generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_suggestions))
|
||||
searchTargets.add(suggestionsHeader)
|
||||
searchTargets.addAll(suggestions.map { generateSearchTarget.getSuggestionTarget(it.resultData as String) })
|
||||
}
|
||||
|
||||
val files = filterByType(wideSearchResults, FILES)
|
||||
if (files.isNotEmpty()) {
|
||||
val filesHeader = generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_files))
|
||||
searchTargets.add(filesHeader)
|
||||
searchTargets.addAll(files.map { generateSearchTarget.getFileInfoSearchItem(it.resultData as IFileInfo) })
|
||||
}
|
||||
|
||||
searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE))
|
||||
searchTargets.add(generateSearchTarget.getStartPageSearchItem(query))
|
||||
generateSearchTarget.getMarketSearchItem(query)?.let { searchTargets.add(it) }
|
||||
|
||||
val adapterItems = transformSearchResults(searchTargets)
|
||||
LawnchairSearchAdapterProvider.setFirstItemQuickLaunch(adapterItems)
|
||||
return ArrayList(adapterItems)
|
||||
}
|
||||
|
||||
private fun filterByType(results: List<SearchResult>, type: String): List<SearchResult> {
|
||||
return results.filter { it.resultType == type }
|
||||
}
|
||||
|
||||
private fun getShortcuts(app: AppInfo): List<ShortcutInfo> {
|
||||
val shortcuts = ShortcutRequest(context.launcher, app.user)
|
||||
.withContainer(app.targetComponent)
|
||||
@@ -142,34 +194,6 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
|
||||
.map { it.referent }
|
||||
}
|
||||
|
||||
private fun resolveMarketSearchActivity(): ComponentKey? {
|
||||
val intent = PackageManagerHelper.getMarketSearchIntent(context, "")
|
||||
val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return null
|
||||
return ComponentKey(launchIntent.component, Process.myUserHandle())
|
||||
}
|
||||
|
||||
private fun getEmptySearchItem(query: String): SearchTargetCompat {
|
||||
val id = "marketSearch:$query"
|
||||
val action = SearchActionCompat.Builder(
|
||||
id,
|
||||
context.getString(R.string.all_apps_search_market_message),
|
||||
)
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_launcher_home))
|
||||
.setIntent(PackageManagerHelper.getMarketSearchIntent(context, query))
|
||||
.build()
|
||||
val extras = bundleOf(
|
||||
if (marketSearchComponent != null) {
|
||||
SearchResultView.EXTRA_ICON_COMPONENT_KEY to marketSearchComponent.toString()
|
||||
} else {
|
||||
SearchResultView.EXTRA_HIDE_ICON to true
|
||||
},
|
||||
SearchResultView.EXTRA_HIDE_SUBTITLE to true,
|
||||
)
|
||||
return createSearchTarget(id, action, extras)
|
||||
}
|
||||
|
||||
private fun Sequence<AppInfo>.filterHiddenApps(query: String): Sequence<AppInfo> {
|
||||
return if (showHiddenAppsInSearch) {
|
||||
this
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.lawnchair.search
|
||||
|
||||
import android.content.SearchRecentSuggestionsProvider
|
||||
|
||||
class LawnchairRecentSuggestionProvider : SearchRecentSuggestionsProvider() {
|
||||
companion object {
|
||||
const val AUTHORITY = "app.lawnchair.search.LawnchairRecentSuggestionProvider"
|
||||
const val MODE = DATABASE_MODE_QUERIES
|
||||
}
|
||||
|
||||
init {
|
||||
setupSuggestions(AUTHORITY, MODE)
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import app.lawnchair.LawnchairLauncher
|
||||
import app.lawnchair.allapps.SearchItemDecorator
|
||||
import app.lawnchair.allapps.SearchResultView
|
||||
import app.lawnchair.allapps.SearchResultView.Companion.EXTRA_QUICK_LAUNCH
|
||||
import app.lawnchair.search.data.SearchResultActionCallBack
|
||||
import com.android.app.search.LayoutType
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.ActivityAllAppsContainerView
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter
|
||||
import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider
|
||||
import com.android.launcher3.util.PackageManagerHelper
|
||||
|
||||
class LawnchairSearchAdapterProvider(
|
||||
launcher: LawnchairLauncher,
|
||||
@@ -28,7 +28,12 @@ class LawnchairSearchAdapterProvider(
|
||||
append(SEARCH_RESULT_ICON_ROW, R.layout.search_result_tall_icon_row)
|
||||
append(SEARCH_RESULT_SMALL_ICON_ROW, R.layout.search_result_small_icon_row)
|
||||
append(SEARCH_RESULT_DIVIDER, R.layout.search_result_divider)
|
||||
append(SEARCH_RESULT_MARKET, R.layout.search_result_market)
|
||||
append(SEARCH_TEXT_HEADER, R.layout.search_result_text_header)
|
||||
append(SEARCH_PEOPLE_TILE, R.layout.search_result_icon_right_left)
|
||||
append(SEARCH_RESULT_FILE_TILE, R.layout.search_result_icon_right_left)
|
||||
append(SEARCH_RESULT_SUGGESTION_TILE, R.layout.search_result_small_icon_row)
|
||||
append(SEARCH_RESULT_SETTINGS_TILE, R.layout.search_result_small_icon_row)
|
||||
append(SEARCH_RESULT_RECENT_TILE, R.layout.search_result_small_icon_row)
|
||||
}
|
||||
private var quickLaunchItem: SearchResultView? = null
|
||||
set(value) {
|
||||
@@ -40,8 +45,17 @@ class LawnchairSearchAdapterProvider(
|
||||
|
||||
override fun onBindView(holder: BaseAllAppsAdapter.ViewHolder, position: Int) {
|
||||
val adapterItem = appsView.mSearchRecyclerView.mApps.adapterItems[position] as SearchAdapterItem
|
||||
adapterItem.setRippleEffect(holder.itemView)
|
||||
val itemView = holder.itemView as SearchResultView
|
||||
itemView.bind(adapterItem.searchTarget, emptyList())
|
||||
itemView.bind(
|
||||
adapterItem.searchTarget,
|
||||
emptyList(),
|
||||
object : SearchResultActionCallBack {
|
||||
override fun action() {
|
||||
appsView.searchUiManager.refreshResults()
|
||||
}
|
||||
},
|
||||
)
|
||||
if (itemView.isQuickLaunch) {
|
||||
quickLaunchItem = itemView
|
||||
}
|
||||
@@ -53,18 +67,6 @@ class LawnchairSearchAdapterProvider(
|
||||
viewType: Int,
|
||||
): BaseAllAppsAdapter.ViewHolder {
|
||||
val view = layoutInflater.inflate(layoutIdMap[viewType], parent, false)
|
||||
if (viewType == SEARCH_RESULT_MARKET) {
|
||||
val marketSearchIntent = PackageManagerHelper
|
||||
.getMarketSearchIntent(
|
||||
view.context,
|
||||
appsView.searchUiManager.editText?.text.toString(),
|
||||
)
|
||||
val marketView = layoutInflater.inflate(R.layout.search_result_market, parent, false)
|
||||
marketView.setOnClickListener {
|
||||
mLauncher.startActivitySafely(it, marketSearchIntent, null)
|
||||
}
|
||||
return BaseAllAppsAdapter.ViewHolder(marketView)
|
||||
}
|
||||
return BaseAllAppsAdapter.ViewHolder(view)
|
||||
}
|
||||
|
||||
@@ -82,15 +84,24 @@ class LawnchairSearchAdapterProvider(
|
||||
private const val SEARCH_RESULT_ICON_ROW = 1 shl 9
|
||||
private const val SEARCH_RESULT_SMALL_ICON_ROW = 1 shl 10
|
||||
private const val SEARCH_RESULT_DIVIDER = 1 shl 11
|
||||
private const val SEARCH_RESULT_MARKET = 1 shl 12
|
||||
private const val SEARCH_TEXT_HEADER = 1 shl 12
|
||||
private const val SEARCH_PEOPLE_TILE = 1 shl 13
|
||||
private const val SEARCH_RESULT_FILE_TILE = 1 shl 14
|
||||
private const val SEARCH_RESULT_SUGGESTION_TILE = 1 shl 15
|
||||
private const val SEARCH_RESULT_SETTINGS_TILE = 1 shl 16
|
||||
private const val SEARCH_RESULT_RECENT_TILE = 1 shl 17
|
||||
|
||||
val viewTypeMap = mapOf(
|
||||
LayoutType.ICON_SINGLE_VERTICAL_TEXT to SEARCH_RESULT_ICON,
|
||||
LayoutType.ICON_HORIZONTAL_TEXT to SEARCH_RESULT_ICON_ROW,
|
||||
LayoutType.SMALL_ICON_HORIZONTAL_TEXT to SEARCH_RESULT_SMALL_ICON_ROW,
|
||||
LayoutType.HORIZONTAL_MEDIUM_TEXT to SEARCH_RESULT_SMALL_ICON_ROW,
|
||||
LayoutType.HORIZONTAL_MEDIUM_TEXT to SEARCH_RESULT_SUGGESTION_TILE,
|
||||
LayoutType.EMPTY_DIVIDER to SEARCH_RESULT_DIVIDER,
|
||||
LayoutType.ICON_HORIZONTAL_TEXT to SEARCH_RESULT_MARKET,
|
||||
LayoutType.TEXT_HEADER to SEARCH_TEXT_HEADER,
|
||||
LayoutType.PEOPLE_TILE to SEARCH_PEOPLE_TILE,
|
||||
LayoutType.THUMBNAIL to SEARCH_RESULT_FILE_TILE,
|
||||
LayoutType.ICON_SLICE to SEARCH_RESULT_SETTINGS_TILE,
|
||||
LayoutType.WIDGET_LIVE to SEARCH_RESULT_RECENT_TILE,
|
||||
)
|
||||
|
||||
fun setFirstItemQuickLaunch(items: List<SearchAdapterItem>) {
|
||||
|
||||
@@ -4,14 +4,39 @@ import android.content.Context
|
||||
import app.lawnchair.LawnchairApp
|
||||
import app.lawnchair.allapps.SearchItemBackground
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import app.lawnchair.preferences2.PreferenceManager2
|
||||
import app.lawnchair.search.SearchTargetCompat.Companion.RESULT_TYPE_APPLICATION
|
||||
import app.lawnchair.search.SearchTargetCompat.Companion.RESULT_TYPE_SHORTCUT
|
||||
import app.lawnchair.search.data.SearchCallback
|
||||
import app.lawnchair.search.data.SearchResult
|
||||
import app.lawnchair.search.data.findContactsByName
|
||||
import app.lawnchair.search.data.findSettingsByNameAndAction
|
||||
import app.lawnchair.search.data.getRecentKeyword
|
||||
import app.lawnchair.search.data.getStartPageSuggestions
|
||||
import app.lawnchair.search.data.queryFilesInMediaStore
|
||||
import app.lawnchair.util.checkAndRequestFilesPermission
|
||||
import app.lawnchair.util.requestContactPermissionGranted
|
||||
import com.android.app.search.LayoutType.EMPTY_DIVIDER
|
||||
import com.android.app.search.LayoutType.HORIZONTAL_MEDIUM_TEXT
|
||||
import com.android.app.search.LayoutType.ICON_HORIZONTAL_TEXT
|
||||
import com.android.app.search.LayoutType.ICON_SINGLE_VERTICAL_TEXT
|
||||
import com.android.app.search.LayoutType.ICON_SLICE
|
||||
import com.android.app.search.LayoutType.PEOPLE_TILE
|
||||
import com.android.app.search.LayoutType.SMALL_ICON_HORIZONTAL_TEXT
|
||||
import com.android.app.search.LayoutType.TEXT_HEADER
|
||||
import com.android.app.search.LayoutType.THUMBNAIL
|
||||
import com.android.app.search.LayoutType.WIDGET_LIVE
|
||||
import com.android.launcher3.BuildConfig
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter
|
||||
import com.android.launcher3.search.SearchAlgorithm
|
||||
import com.patrykmichalik.opto.core.onEach
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
|
||||
sealed class LawnchairSearchAlgorithm(
|
||||
protected val context: Context,
|
||||
@@ -55,20 +80,64 @@ sealed class LawnchairSearchAlgorithm(
|
||||
.filter { LawnchairSearchAdapterProvider.viewTypeMap[it.layoutType] != null }
|
||||
.removeDuplicateDividers()
|
||||
.toList()
|
||||
return filtered
|
||||
.mapIndexedNotNull { index, target ->
|
||||
val isFirst = index == 0 || filtered[index - 1].isDivider
|
||||
val isLast = index == filtered.lastIndex || filtered[index + 1].isDivider
|
||||
val background = when {
|
||||
target.layoutType == ICON_SINGLE_VERTICAL_TEXT -> iconBackground
|
||||
target.layoutType == EMPTY_DIVIDER -> iconBackground
|
||||
isFirst && isLast -> normalBackground
|
||||
isFirst -> topBackground
|
||||
isLast -> bottomBackground
|
||||
else -> centerBackground
|
||||
}
|
||||
SearchAdapterItem.createAdapterItem(target, background)
|
||||
}
|
||||
|
||||
val smallIconIndices = findIndices(filtered, SMALL_ICON_HORIZONTAL_TEXT)
|
||||
val iconRowIndices = findIndices(filtered, ICON_HORIZONTAL_TEXT)
|
||||
val peopleTileIndices = findIndices(filtered, PEOPLE_TILE)
|
||||
val suggestionIndices = findIndices(filtered, HORIZONTAL_MEDIUM_TEXT)
|
||||
val fileIndices = findIndices(filtered, THUMBNAIL)
|
||||
val settingIndices = findIndices(filtered, ICON_SLICE)
|
||||
val recentIndices = findIndices(filtered, WIDGET_LIVE)
|
||||
|
||||
return filtered.mapIndexedNotNull { index, target ->
|
||||
val isFirst = index == 0 || filtered[index - 1].isDivider
|
||||
val isLast = index == filtered.lastIndex || filtered[index + 1].isDivider
|
||||
val background = getBackground(
|
||||
target.layoutType, index, isFirst, isLast,
|
||||
smallIconIndices, iconRowIndices, peopleTileIndices, suggestionIndices, fileIndices, settingIndices, recentIndices,
|
||||
)
|
||||
SearchAdapterItem.createAdapterItem(target, background)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findIndices(filtered: List<SearchTargetCompat>, layoutType: String): List<Int> {
|
||||
return filtered.indices.filter {
|
||||
filtered[it].layoutType == layoutType
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBackground(
|
||||
layoutType: String,
|
||||
index: Int,
|
||||
isFirst: Boolean,
|
||||
isLast: Boolean,
|
||||
smallIconIndices: List<Int>,
|
||||
iconRowIndices: List<Int>,
|
||||
peopleTileIndices: List<Int>,
|
||||
suggestionIndices: List<Int>,
|
||||
fileIndices: List<Int>,
|
||||
settingIndices: List<Int>,
|
||||
recentIndices: List<Int>,
|
||||
): SearchItemBackground = when {
|
||||
layoutType == TEXT_HEADER || layoutType == ICON_SINGLE_VERTICAL_TEXT || layoutType == EMPTY_DIVIDER -> iconBackground
|
||||
layoutType == SMALL_ICON_HORIZONTAL_TEXT -> getGroupedBackground(index, smallIconIndices)
|
||||
layoutType == ICON_HORIZONTAL_TEXT -> getGroupedBackground(index, iconRowIndices)
|
||||
layoutType == PEOPLE_TILE -> getGroupedBackground(index, peopleTileIndices)
|
||||
layoutType == HORIZONTAL_MEDIUM_TEXT -> getGroupedBackground(index, suggestionIndices)
|
||||
layoutType == THUMBNAIL -> getGroupedBackground(index, fileIndices)
|
||||
layoutType == ICON_SLICE -> getGroupedBackground(index, settingIndices)
|
||||
layoutType == WIDGET_LIVE -> getGroupedBackground(index, recentIndices)
|
||||
isFirst && isLast -> normalBackground
|
||||
isFirst -> topBackground
|
||||
isLast -> bottomBackground
|
||||
else -> centerBackground
|
||||
}
|
||||
|
||||
private fun getGroupedBackground(index: Int, indices: List<Int>): SearchItemBackground = when {
|
||||
indices.size == 1 -> normalBackground
|
||||
index == indices.first() -> topBackground
|
||||
index == indices.last() -> bottomBackground
|
||||
else -> centerBackground
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -92,6 +161,91 @@ sealed class LawnchairSearchAlgorithm(
|
||||
else -> LawnchairAppSearchAlgorithm(context)
|
||||
}
|
||||
}
|
||||
|
||||
private var maxPeopleCount = 10
|
||||
private var maxSuggestionCount = 3
|
||||
private var maxFilesCount = 3
|
||||
private var maxSettingsEntryCount = 5
|
||||
private var maxRecentResultCount = 2
|
||||
val coroutineScope = CoroutineScope(context = Dispatchers.IO)
|
||||
val pref2 = PreferenceManager2.getInstance(context)
|
||||
|
||||
init {
|
||||
pref2.maxFileResultCount.onEach(launchIn = coroutineScope) {
|
||||
maxFilesCount = it
|
||||
}
|
||||
pref2.maxPeopleResultCount.onEach(launchIn = coroutineScope) {
|
||||
maxPeopleCount = it
|
||||
}
|
||||
pref2.maxSuggestionResultCount.onEach(launchIn = coroutineScope) {
|
||||
maxSuggestionCount = it
|
||||
}
|
||||
pref2.maxSettingsEntryResultCount.onEach(launchIn = coroutineScope) {
|
||||
maxSettingsEntryCount = it
|
||||
}
|
||||
pref2.maxRecentResultCount.onEach(launchIn = coroutineScope) {
|
||||
maxRecentResultCount = it
|
||||
}
|
||||
}
|
||||
|
||||
protected suspend fun performDeviceWideSearch(query: String, prefs: PreferenceManager): MutableList<SearchResult> = withContext(Dispatchers.IO) {
|
||||
val results = ArrayList<SearchResult>()
|
||||
|
||||
if (prefs.searchResultPeople.get()) {
|
||||
if (requestContactPermissionGranted(context, prefs)) {
|
||||
val contactResults = findContactsByName(context, query, maxPeopleCount)
|
||||
results.addAll(contactResults.map { SearchResult(CONTACT, it) })
|
||||
}
|
||||
}
|
||||
|
||||
if (prefs.searchResultFiles.get()) {
|
||||
if (checkAndRequestFilesPermission(context, prefs)) {
|
||||
val fileResults = queryFilesInMediaStore(context, keyword = query, maxResult = maxFilesCount).toList()
|
||||
results.addAll(fileResults.map { SearchResult(FILES, it) })
|
||||
}
|
||||
}
|
||||
|
||||
if (prefs.searchResultSettingsEntry.get()) {
|
||||
val settingResult = findSettingsByNameAndAction(query, maxSettingsEntryCount)
|
||||
results.addAll(settingResult.map { SearchResult(SETTING, it) })
|
||||
}
|
||||
|
||||
if (prefs.searchResultStartPageSuggestion.get()) {
|
||||
val startPageSuggestions = async {
|
||||
try {
|
||||
withTimeout(3000) {
|
||||
getStartPageSuggestions(query, maxSuggestionCount)
|
||||
}
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
results.addAll(startPageSuggestions.await().map { SearchResult(SUGGESTION, it) })
|
||||
}
|
||||
|
||||
if (prefs.searchResulRecentSuggestion.get()) {
|
||||
getRecentKeyword(
|
||||
context,
|
||||
query,
|
||||
maxRecentResultCount,
|
||||
object : SearchCallback {
|
||||
override fun onSearchLoaded(items: List<Any>) {
|
||||
results.addAll(items.map { SearchResult(RECENT_KEYWORD, it) })
|
||||
}
|
||||
|
||||
override fun onSearchFailed(error: String) {
|
||||
results.add(SearchResult(ERROR, error))
|
||||
}
|
||||
|
||||
override fun onLoading() {
|
||||
results.add(SearchResult(LOADING, "Loading"))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<SearchTargetCompat>.removeDuplicateDividers(): Sequence<SearchTargetCompat> {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package app.lawnchair.search
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.RoundRectShape
|
||||
import android.view.View
|
||||
import app.lawnchair.allapps.SearchItemBackground
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter
|
||||
|
||||
@@ -9,6 +15,30 @@ data class SearchAdapterItem(
|
||||
val viewType: Int,
|
||||
) : BaseAllAppsAdapter.AdapterItem(viewType) {
|
||||
|
||||
fun setRippleEffect(child: View) {
|
||||
val shape = RoundRectShape(background?.cornerRadii, null, null)
|
||||
val shapeDrawable = ShapeDrawable(shape)
|
||||
val colorDefault = background?.groupHighlight ?: 0
|
||||
val colorPressed = background?.focusHighlight ?: 0
|
||||
|
||||
val colorStateList = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(android.R.attr.state_pressed),
|
||||
intArrayOf(-android.R.attr.state_pressed),
|
||||
),
|
||||
intArrayOf(
|
||||
colorPressed,
|
||||
colorDefault,
|
||||
),
|
||||
)
|
||||
|
||||
shapeDrawable.paint.color = colorDefault
|
||||
val inset = 3
|
||||
val insetDrawable = InsetDrawable(shapeDrawable, 0, inset, 0, inset)
|
||||
val rippleDrawable = RippleDrawable(colorStateList, insetDrawable, null)
|
||||
child.background = rippleDrawable
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun createAdapterItem(
|
||||
|
||||
@@ -262,7 +262,20 @@ data class SearchTargetCompat(
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(value = [RESULT_TYPE_APPLICATION, RESULT_TYPE_SHORTCUT, RESULT_TYPE_SLICE, RESULT_TYPE_WIDGETS])
|
||||
@IntDef(
|
||||
value = [
|
||||
RESULT_TYPE_APPLICATION,
|
||||
RESULT_TYPE_SHORTCUT,
|
||||
RESULT_TYPE_SLICE,
|
||||
RESULT_TYPE_WIDGETS,
|
||||
RESULT_TYPE_SUGGESTIONS,
|
||||
RESULT_TYPE_REDIRECTION,
|
||||
RESULT_TYPE_SECTION_HEADER,
|
||||
RESULT_TYPE_CONTACT_TILE,
|
||||
RESULT_TYPE_FILE_TILE,
|
||||
RESULT_TYPE_SETTING_TILE,
|
||||
],
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class SearchResultType
|
||||
|
||||
@@ -271,6 +284,12 @@ data class SearchTargetCompat(
|
||||
const val RESULT_TYPE_SHORTCUT = 1 shl 1
|
||||
const val RESULT_TYPE_SLICE = 1 shl 2
|
||||
const val RESULT_TYPE_WIDGETS = 1 shl 3
|
||||
const val RESULT_TYPE_SUGGESTIONS = 1 shl 4
|
||||
const val RESULT_TYPE_REDIRECTION = 1 shl 5
|
||||
const val RESULT_TYPE_SECTION_HEADER = 1 shl 6
|
||||
const val RESULT_TYPE_CONTACT_TILE = 1 shl 7
|
||||
const val RESULT_TYPE_FILE_TILE = 1 shl 8
|
||||
const val RESULT_TYPE_SETTING_TILE = 1 shl 9
|
||||
|
||||
fun wrap(target: SearchTarget): SearchTargetCompat = SearchTargetCompat(target)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,22 @@ import androidx.core.os.bundleOf
|
||||
import com.android.app.search.LayoutType
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.util.ComponentKey
|
||||
import java.security.MessageDigest
|
||||
import okio.ByteString
|
||||
|
||||
// We're generate hash key as alt in id, so we can avoid
|
||||
// the wrong action when click,
|
||||
// TODO remove when we properly manage creating search target
|
||||
private fun generateHashKey(input: String): String =
|
||||
ByteString.of(*MessageDigest.getInstance("SHA-256").digest(input.toByteArray())).hex()
|
||||
|
||||
fun createSearchTarget(appInfo: AppInfo, asRow: Boolean = false): SearchTargetCompat {
|
||||
val componentName = appInfo.componentName
|
||||
val user = appInfo.user
|
||||
return SearchTargetCompat.Builder(
|
||||
SearchTargetCompat.RESULT_TYPE_APPLICATION,
|
||||
if (asRow) LayoutType.ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT,
|
||||
ComponentKey(componentName, user).toString(),
|
||||
if (asRow) LayoutType.SMALL_ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT,
|
||||
generateHashKey(ComponentKey(componentName, user).toString()),
|
||||
)
|
||||
.setPackageName(componentName.packageName)
|
||||
.setUserHandle(user)
|
||||
@@ -26,7 +34,7 @@ fun createSearchTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat {
|
||||
return SearchTargetCompat.Builder(
|
||||
SearchTargetCompat.RESULT_TYPE_SHORTCUT,
|
||||
LayoutType.SMALL_ICON_HORIZONTAL_TEXT,
|
||||
"${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}",
|
||||
"shortcut_" + generateHashKey("${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}"),
|
||||
)
|
||||
.setShortcutInfo(shortcutInfo)
|
||||
.setUserHandle(shortcutInfo.userHandle)
|
||||
@@ -34,13 +42,38 @@ fun createSearchTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat {
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createSearchTarget(id: String, action: SearchActionCompat, extras: Bundle = Bundle()): SearchTargetCompat {
|
||||
fun createSearchTarget(
|
||||
id: String,
|
||||
action: SearchActionCompat,
|
||||
pkg: String,
|
||||
extras: Bundle = Bundle(),
|
||||
): SearchTargetCompat {
|
||||
return SearchTargetCompat.Builder(
|
||||
SearchTargetCompat.RESULT_TYPE_SHORTCUT,
|
||||
SearchTargetCompat.RESULT_TYPE_REDIRECTION,
|
||||
LayoutType.ICON_HORIZONTAL_TEXT,
|
||||
id,
|
||||
generateHashKey(id),
|
||||
)
|
||||
.setPackageName("")
|
||||
.setPackageName(pkg)
|
||||
.setUserHandle(Process.myUserHandle())
|
||||
.setSearchAction(action)
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createSearchTarget(
|
||||
id: String,
|
||||
action: SearchActionCompat,
|
||||
layoutType: String,
|
||||
targetCompat: Int,
|
||||
pkg: String,
|
||||
extras: Bundle = Bundle(),
|
||||
): SearchTargetCompat {
|
||||
return SearchTargetCompat.Builder(
|
||||
targetCompat,
|
||||
layoutType,
|
||||
generateHashKey(id),
|
||||
)
|
||||
.setPackageName(pkg)
|
||||
.setUserHandle(Process.myUserHandle())
|
||||
.setSearchAction(action)
|
||||
.setExtras(extras)
|
||||
@@ -57,3 +90,18 @@ fun createDividerTarget(): SearchTargetCompat {
|
||||
.setUserHandle(Process.myUserHandle())
|
||||
.build()
|
||||
}
|
||||
|
||||
const val START_PAGE = "startpage"
|
||||
const val MARKET_STORE = "marketstore"
|
||||
const val SUGGESTION = "suggestion"
|
||||
const val HEADER = "header"
|
||||
const val CONTACT = "contact"
|
||||
const val FILES = "files"
|
||||
const val SPACE = "space"
|
||||
const val SPACE_MINI = "space_mini"
|
||||
const val LOADING = "loading"
|
||||
const val ERROR = "error"
|
||||
const val SETTING = "setting"
|
||||
const val SHORTCUT = "shortcut"
|
||||
const val RECENT_KEYWORD = "recent_keyword"
|
||||
const val HEADER_JUSTIFY = "header_justify"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.lawnchair.search.data
|
||||
|
||||
interface SearchCallback {
|
||||
fun onSearchLoaded(items: List<Any>)
|
||||
fun onSearchFailed(error: String)
|
||||
fun onLoading()
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package app.lawnchair.search.data
|
||||
|
||||
import android.annotation.DrawableRes
|
||||
import app.lawnchair.util.androidPkgTypes
|
||||
import app.lawnchair.util.archiveFileTypes
|
||||
import app.lawnchair.util.audioFileTypes
|
||||
import app.lawnchair.util.documentFileTypes
|
||||
import app.lawnchair.util.imageFileTypes
|
||||
import app.lawnchair.util.videoFileTypes
|
||||
import com.android.launcher3.R
|
||||
|
||||
data class ContactInfo(
|
||||
val contactId: String,
|
||||
val name: String,
|
||||
var number: String,
|
||||
val phoneBookLabel: String,
|
||||
val uri: String,
|
||||
var packages: String,
|
||||
)
|
||||
|
||||
const val CONTACT_ACCOUNT_ID = "contact.id"
|
||||
const val CONTACT_ACCOUNT_MIME = "contact.mime"
|
||||
const val CONTACT_ACCOUNT_NAME = "contact.account.name"
|
||||
const val CONTACT_ACCOUNT_TITLE = "contact.title"
|
||||
const val CONTACT_ACCOUNT_TYPE = "contact.account.type"
|
||||
|
||||
val EXCLUDED_MIME_TYPES = arrayOf(
|
||||
"vnd.android.cursor.item/name",
|
||||
"vnd.android.cursor.item/nickname",
|
||||
"vnd.android.cursor.item/note",
|
||||
"vnd.android.cursor.item/photo",
|
||||
"vnd.com.google.cursor.item/contact_misc",
|
||||
"vnd.android.cursor.item/identity",
|
||||
"vnd.android.cursor.item/website",
|
||||
)
|
||||
|
||||
data class SettingInfo(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val action: String,
|
||||
val requiresUri: Boolean = false,
|
||||
)
|
||||
|
||||
data class RecentKeyword(
|
||||
val data: Map<String, String>,
|
||||
) {
|
||||
fun getValueByKey(key: String): String? {
|
||||
return data[key]
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface IFileInfo {
|
||||
val path: String
|
||||
val name: String
|
||||
val size: Long
|
||||
val dateModified: Long
|
||||
}
|
||||
|
||||
data class FolderInfo(
|
||||
override val path: String,
|
||||
override val name: String,
|
||||
override val size: Long,
|
||||
override val dateModified: Long,
|
||||
) : IFileInfo
|
||||
|
||||
data class FileInfo(
|
||||
val fileId: String,
|
||||
override val path: String,
|
||||
override val name: String,
|
||||
override val size: Long,
|
||||
override val dateModified: Long,
|
||||
val mimeType: String?,
|
||||
) : IFileInfo {
|
||||
@get:DrawableRes
|
||||
val iconRes = when (val mime = mimeType.orEmpty()) {
|
||||
in imageFileTypes.values -> R.drawable.ic_file_image
|
||||
in videoFileTypes.values -> R.drawable.ic_file_video
|
||||
in audioFileTypes.values -> R.drawable.ic_file_music
|
||||
in androidPkgTypes.values -> R.drawable.ic_file_app
|
||||
in archiveFileTypes.values -> R.drawable.ic_file_zip
|
||||
in documentFileTypes.values -> when {
|
||||
mime.contains("excel") || mime.contains("csv") -> R.drawable.ic_file_excel
|
||||
mime.contains("word") -> R.drawable.ic_file_word
|
||||
mime.contains("powerpoint") -> R.drawable.ic_file_powerpoint
|
||||
mime.contains("pdf") -> R.drawable.ic_file_pdf
|
||||
mime.contains("srt") -> R.drawable.ic_file_subtitle
|
||||
else -> R.drawable.ic_file_text
|
||||
}
|
||||
else -> R.drawable.ic_file_unknown
|
||||
}
|
||||
|
||||
companion object {
|
||||
val FileInfo.isMediaType: Boolean get() {
|
||||
return mimeType in videoFileTypes.values ||
|
||||
mimeType in audioFileTypes.values
|
||||
}
|
||||
|
||||
val FileInfo.isImageType: Boolean get() = mimeType in imageFileTypes.values
|
||||
|
||||
val FileInfo.isUnknownType: Boolean get() {
|
||||
return mimeType !in imageFileTypes.values &&
|
||||
mimeType !in videoFileTypes.values &&
|
||||
mimeType !in audioFileTypes.values &&
|
||||
mimeType !in androidPkgTypes.values &&
|
||||
mimeType !in archiveFileTypes.values &&
|
||||
mimeType !in documentFileTypes.values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class SearchResult(
|
||||
val resultType: String,
|
||||
val resultData: Any,
|
||||
)
|
||||
@@ -0,0 +1,332 @@
|
||||
package app.lawnchair.search.data
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import app.lawnchair.search.LawnchairRecentSuggestionProvider
|
||||
import app.lawnchair.search.data.suggestion.StartPageService
|
||||
import app.lawnchair.util.exists
|
||||
import app.lawnchair.util.isDirectory
|
||||
import app.lawnchair.util.isHidden
|
||||
import app.lawnchair.util.isRegularFile
|
||||
import app.lawnchair.util.kotlinxJson
|
||||
import app.lawnchair.util.mimeType2Extension
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.ResponseBody
|
||||
import okio.Path.Companion.toPath
|
||||
import org.json.JSONArray
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
|
||||
private val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://www.startpage.com/")
|
||||
.addConverterFactory(kotlinxJson.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
val startPageService: StartPageService = retrofit.create()
|
||||
|
||||
suspend fun getStartPageSuggestions(query: String, max: Int): List<String> = withContext(Dispatchers.IO) {
|
||||
if (query.isEmpty() || query.isBlank() || max <= 0) {
|
||||
return@withContext emptyList()
|
||||
}
|
||||
|
||||
try {
|
||||
val response: Response<ResponseBody> = startPageService.getStartPageSuggestions(
|
||||
query = query,
|
||||
segment = "startpage.lawnchair",
|
||||
partner = "lawnchair",
|
||||
format = "opensearch",
|
||||
)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val responseBody = response.body()?.string()
|
||||
return@withContext JSONArray(responseBody).optJSONArray(1)?.let { array ->
|
||||
(0 until array.length()).take(max).map { array.getString(it) }
|
||||
} ?: emptyList()
|
||||
} else {
|
||||
Log.d("Failed to retrieve suggestions", ": ${response.code()}")
|
||||
return@withContext emptyList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Exception", "Error during suggestion retrieval: ${e.message}")
|
||||
return@withContext emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getRecentKeyword(context: Context, query: String, max: Int, callback: SearchCallback) {
|
||||
try {
|
||||
if (query.isEmpty() || query.isBlank() || max <= 0) {
|
||||
callback.onSearchLoaded(emptyList())
|
||||
return
|
||||
}
|
||||
|
||||
callback.onLoading()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val contentResolver: ContentResolver = context.contentResolver
|
||||
val uri: Uri = Uri.parse("content://${LawnchairRecentSuggestionProvider.AUTHORITY}/suggestions")
|
||||
val cursor: Cursor? = contentResolver.query(uri, null, null, null, null)
|
||||
val recentKeywords = mutableListOf<RecentKeyword>()
|
||||
|
||||
cursor?.use {
|
||||
val columnCount = it.columnCount
|
||||
|
||||
while (it.moveToNext()) {
|
||||
val recentKeywordData = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0 until columnCount) {
|
||||
val columnName = it.getColumnName(i)
|
||||
val columnValue = it.getString(i) ?: ""
|
||||
recentKeywordData[columnName] = columnValue
|
||||
}
|
||||
|
||||
recentKeywords.add(RecentKeyword(recentKeywordData))
|
||||
}
|
||||
}
|
||||
callback.onSearchLoaded(recentKeywords.asReversed().take(max))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Exception", "Error during recent keyword retrieval: ${e.message}")
|
||||
callback.onSearchFailed("Error during recent keyword retrieval: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun findSettingsByNameAndAction(query: String, max: Int): List<SettingInfo> = try {
|
||||
if (query.isBlank() || max <= 0) {
|
||||
emptyList()
|
||||
} else {
|
||||
withContext(
|
||||
Dispatchers.IO + CoroutineExceptionHandler { _, e ->
|
||||
Log.e("SettingSearch", "Something went wrong ", e)
|
||||
},
|
||||
) {
|
||||
Settings::class.java.fields
|
||||
.asSequence()
|
||||
.filter { it.type == String::class.java && Modifier.isStatic(it.modifiers) && it.name.startsWith("ACTION_") }
|
||||
.map { it.name to it.get(null) as String }
|
||||
.filter { (name, action) ->
|
||||
name.contains(query, ignoreCase = true) &&
|
||||
!action.contains("REQUEST", ignoreCase = true) &&
|
||||
!name.contains("REQUEST", ignoreCase = true) &&
|
||||
!action.contains("PERMISSION", ignoreCase = true) &&
|
||||
!name.contains("DETAIL", ignoreCase = true) &&
|
||||
!name.contains("REMOTE", ignoreCase = true)
|
||||
}
|
||||
.map { (name, action) ->
|
||||
val id = name + action
|
||||
val requiresUri = action.contains("URI")
|
||||
SettingInfo(id, name, action, requiresUri)
|
||||
}
|
||||
.toList().take(max)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("SettingSearch", "Something went wrong ", e)
|
||||
emptyList()
|
||||
}
|
||||
|
||||
suspend fun findContactsByName(context: Context, query: String, max: Int): List<ContactInfo> {
|
||||
try {
|
||||
if (query.isEmpty() || query.isBlank() || max <= 0) return emptyList()
|
||||
val exceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||
Log.e("ContactSearch", "Something went wrong ", e)
|
||||
}
|
||||
return withContext(Dispatchers.IO + exceptionHandler) {
|
||||
val contactMap = HashMap<String, ContactInfo>()
|
||||
|
||||
val projection = arrayOf(
|
||||
ContactsContract.Data._ID,
|
||||
ContactsContract.Data.CONTACT_ID,
|
||||
ContactsContract.Data.DISPLAY_NAME,
|
||||
ContactsContract.Data.DATA1,
|
||||
ContactsContract.Data.DATA3,
|
||||
ContactsContract.Data.DATA5,
|
||||
"phonebook_label",
|
||||
"account_type",
|
||||
"account_name",
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.Data.PHOTO_URI,
|
||||
)
|
||||
|
||||
val selection = ContactsContract.Data.DISPLAY_NAME + " LIKE ?"
|
||||
val selectionArgs = arrayOf("%$query%")
|
||||
|
||||
context.contentResolver.query(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
)?.use {
|
||||
while (it.moveToNext() && contactMap.size < max) {
|
||||
val contactIdIndex = it.getColumnIndex(ContactsContract.Data.CONTACT_ID)
|
||||
val displayNameIndex = it.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)
|
||||
val data1Index = it.getColumnIndex(ContactsContract.Data.DATA1)
|
||||
val data3Index = it.getColumnIndex(ContactsContract.Data.DATA3)
|
||||
val data5Index = it.getColumnIndex(ContactsContract.Data.DATA5)
|
||||
val phonebookLabelIndex = it.getColumnIndex("phonebook_label")
|
||||
val accountTypeIndex = it.getColumnIndex("account_type")
|
||||
val accountNameIndex = it.getColumnIndex("account_name")
|
||||
val mimeTypeIndex = it.getColumnIndex(ContactsContract.Data.MIMETYPE)
|
||||
val photoUriIndex = it.getColumnIndex(ContactsContract.Data.PHOTO_URI)
|
||||
val contactId = it.getString(contactIdIndex)
|
||||
val displayName = it.getString(displayNameIndex)
|
||||
val data1 = it.getString(data1Index)
|
||||
val data3 = it.getString(data3Index)
|
||||
val data5 = it.getString(data5Index)
|
||||
val phonebookLabel = it.getString(phonebookLabelIndex)
|
||||
val accountType = it.getString(accountTypeIndex)
|
||||
val accountName = it.getString(accountNameIndex)
|
||||
val mimeType = it.getString(mimeTypeIndex)
|
||||
val photoUri = it.getString(photoUriIndex)
|
||||
val phoneNumber = data3 ?: data5 ?: data1
|
||||
val key = contactId ?: phoneNumber
|
||||
val imageUri = photoUri ?: ""
|
||||
val phonebookLabel2 = phonebookLabel ?: ""
|
||||
val pkg = contactId + displayName + phoneNumber
|
||||
if (key != null && !EXCLUDED_MIME_TYPES.contains(mimeType)) {
|
||||
contactMap[key] = ContactInfo(
|
||||
contactId,
|
||||
displayName,
|
||||
phoneNumber,
|
||||
phonebookLabel2,
|
||||
imageUri,
|
||||
pkg,
|
||||
)
|
||||
} else {
|
||||
if (contactMap.containsKey(contactId)) {
|
||||
val existingContact = contactMap[contactId]
|
||||
val jsonArray = buildJsonArray {
|
||||
add(
|
||||
buildJsonObject {
|
||||
put(CONTACT_ACCOUNT_ID, key)
|
||||
put(CONTACT_ACCOUNT_TITLE, data5)
|
||||
put(CONTACT_ACCOUNT_NAME, accountName)
|
||||
put(CONTACT_ACCOUNT_TYPE, accountType)
|
||||
put(CONTACT_ACCOUNT_MIME, mimeType)
|
||||
},
|
||||
)
|
||||
}
|
||||
existingContact?.packages = jsonArray.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
contactMap.values.toList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ContactSearch", "Something went wrong ", e)
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun queryFilesInMediaStore(
|
||||
context: Context,
|
||||
uri: Uri = MediaStore.Files.getContentUri("external"),
|
||||
path: String = "",
|
||||
keyword: String,
|
||||
maxResult: Int,
|
||||
mimes: Array<String>? = null,
|
||||
): Sequence<IFileInfo> = withContext(Dispatchers.IO) {
|
||||
val selection = "${commonProjection[0]} like ? AND ${commonProjection[0]} like ? ".let {
|
||||
if (mimes == null) it else it + "AND ${commonProjection[4]} IN (${mimes.selectionArgsPlaceHolder})"
|
||||
}
|
||||
val selectionArgs = arrayOf("%$path%", "%$keyword%").let {
|
||||
if (mimes == null) it else it + mimes
|
||||
}
|
||||
getFileListFromMediaStore(
|
||||
context,
|
||||
uri,
|
||||
commonProjection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
maxResult = maxResult,
|
||||
) { cursor ->
|
||||
val filePath = cursor.getString(cursor.getColumnIndexOrThrow(commonProjection[0])).toPath()
|
||||
if (filePath.isDirectory()) getFolderBean(cursor) else getFileBean(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
private val Array<String>.selectionArgsPlaceHolder: String get() = Array(size) { "?" }.joinToString()
|
||||
|
||||
private val commonProjection = arrayOf(
|
||||
MediaStore.MediaColumns.DATA,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
MediaStore.MediaColumns.TITLE,
|
||||
MediaStore.MediaColumns._ID,
|
||||
)
|
||||
|
||||
private suspend inline fun <T : Any> getFileListFromMediaStore(
|
||||
context: Context,
|
||||
uri: Uri,
|
||||
projection: Array<String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?,
|
||||
sortOrder: String = "${commonProjection[3]} DESC",
|
||||
maxResult: Int,
|
||||
crossinline body: (Cursor) -> T?,
|
||||
): Sequence<T> = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
|
||||
?.use { cursor ->
|
||||
var count = 0
|
||||
buildList {
|
||||
while (cursor.moveToNext()) {
|
||||
val bean = body(cursor) ?: continue
|
||||
add(bean)
|
||||
count++
|
||||
if (count >= maxResult) break
|
||||
}
|
||||
}.asSequence()
|
||||
} ?: emptySequence()
|
||||
}
|
||||
|
||||
private fun getFileBean(cursor: Cursor): IFileInfo? = cursor.run {
|
||||
val mimeType = getString(getColumnIndexOrThrow(commonProjection[4]))
|
||||
val title = getString(getColumnIndexOrThrow(commonProjection[1]))
|
||||
?: getString(getColumnIndexOrThrow(commonProjection[5]))?.let {
|
||||
if (mimeType == null) it else "$it.${mimeType.mimeType2Extension()}"
|
||||
} ?: return null
|
||||
val path = getString(getColumnIndexOrThrow(commonProjection[0])).toPath()
|
||||
val fileId = getString(getColumnIndexOrThrow(commonProjection[6]))
|
||||
if (!path.isRegularFile() || path.isHidden) return null
|
||||
val dateModified = getLong(getColumnIndexOrThrow(commonProjection[3])) * 1000
|
||||
return FileInfo(
|
||||
fileId,
|
||||
path.toString(),
|
||||
title,
|
||||
getLong(getColumnIndexOrThrow(commonProjection[2])),
|
||||
dateModified,
|
||||
mimeType,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFolderBean(cursor: Cursor): FolderInfo? = cursor.run {
|
||||
val title = getString(getColumnIndexOrThrow(commonProjection[1]))
|
||||
?: getString(getColumnIndexOrThrow(commonProjection[5])) ?: return null
|
||||
val path = getString(getColumnIndexOrThrow(commonProjection[0])).toPath()
|
||||
if (!path.exists || path.isHidden) return null
|
||||
val dateModified = getLong(getColumnIndexOrThrow(commonProjection[3])) * 1000
|
||||
return FolderInfo(
|
||||
path.toString(),
|
||||
title,
|
||||
getLong(getColumnIndexOrThrow(commonProjection[2])),
|
||||
dateModified,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package app.lawnchair.search.data
|
||||
|
||||
interface SearchResultActionCallBack {
|
||||
fun action()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package app.lawnchair.search.data.suggestion
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface StartPageService {
|
||||
@GET("suggestions")
|
||||
suspend fun getStartPageSuggestions(
|
||||
@Query("q") query: String,
|
||||
@Query("segment") segment: String,
|
||||
@Query("partner") partner: String,
|
||||
@Query("format") format: String,
|
||||
): Response<ResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package app.lawnchair.theme.color
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun generateColor(context: Context): Int {
|
||||
val accentColors = listOf(
|
||||
ColorTokens.Accent1_50,
|
||||
ColorTokens.Accent1_100,
|
||||
ColorTokens.Accent1_200,
|
||||
ColorTokens.Accent1_300,
|
||||
ColorTokens.Accent1_500,
|
||||
ColorTokens.Accent1_600,
|
||||
ColorTokens.Accent2_50,
|
||||
ColorTokens.Accent2_100,
|
||||
ColorTokens.Accent2_500,
|
||||
ColorTokens.Accent2_600,
|
||||
ColorTokens.Accent3_50,
|
||||
ColorTokens.Accent3_100,
|
||||
)
|
||||
val randomAccent = accentColors.random()
|
||||
return randomAccent.resolveColor(context)
|
||||
}
|
||||
@@ -32,6 +32,10 @@ import app.lawnchair.ui.preferences.components.PreferenceLayout
|
||||
import app.lawnchair.ui.preferences.components.SliderPreference
|
||||
import app.lawnchair.ui.preferences.components.SuggestionsPreference
|
||||
import app.lawnchair.ui.preferences.components.SwitchPreference
|
||||
import app.lawnchair.util.checkAndRequestFilesPermission
|
||||
import app.lawnchair.util.contactPermissionGranted
|
||||
import app.lawnchair.util.filesAndStorageGranted
|
||||
import app.lawnchair.util.requestContactPermissionGranted
|
||||
import com.android.launcher3.R
|
||||
|
||||
object AppDrawerRoutes {
|
||||
@@ -48,7 +52,9 @@ fun NavGraphBuilder.appDrawerGraph(route: String) {
|
||||
fun AppDrawerPreferences() {
|
||||
val prefs = preferenceManager()
|
||||
val prefs2 = preferenceManager2()
|
||||
val resources = LocalContext.current.resources
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
|
||||
PreferenceLayout(label = stringResource(id = R.string.app_drawer_label)) {
|
||||
PreferenceGroup(heading = stringResource(id = R.string.general_label)) {
|
||||
SliderPreference(
|
||||
@@ -115,6 +121,108 @@ fun AppDrawerPreferences() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isDeviceSearch = prefs.performWideSearchExperimental.get() && showDrawerSearchBar.state.value
|
||||
if (isDeviceSearch) {
|
||||
PreferenceGroup(heading = stringResource(id = R.string.pref_advance_search_category)) {
|
||||
SwitchPreference(
|
||||
adapter = prefs2.performWideSearch.getAdapter(),
|
||||
label = stringResource(id = R.string.perform_wide_search_title),
|
||||
)
|
||||
ExpandAndShrink(visible = prefs2.performWideSearch.getAdapter().state.value) {
|
||||
DividerColumn {
|
||||
SwitchPreference(
|
||||
adapter = prefs.searchResultFiles.getAdapter(),
|
||||
label = stringResource(id = R.string.perform_wide_search_file),
|
||||
description = stringResource(
|
||||
id = if (
|
||||
filesAndStorageGranted(context)
|
||||
) {
|
||||
R.string.all_apps_search_result_files_description
|
||||
} else {
|
||||
R.string.warn_files_permission_content
|
||||
},
|
||||
),
|
||||
onClick = { checkAndRequestFilesPermission(context, prefs) },
|
||||
)
|
||||
ExpandAndShrink(visible = prefs.searchResultFiles.getAdapter().state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.max_file_result_count_title),
|
||||
adapter = prefs2.maxFileResultCount.getAdapter(),
|
||||
step = 1,
|
||||
valueRange = 3..10,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
adapter = prefs.searchResultPeople.getAdapter(),
|
||||
label = stringResource(id = R.string.search_pref_result_people_title),
|
||||
description = stringResource(
|
||||
id = if (
|
||||
contactPermissionGranted(context)
|
||||
) {
|
||||
R.string.all_apps_search_result_contacts_description
|
||||
} else {
|
||||
R.string.warn_contact_permission_content
|
||||
},
|
||||
),
|
||||
onClick = { requestContactPermissionGranted(context, prefs) },
|
||||
)
|
||||
ExpandAndShrink(visible = prefs.searchResultPeople.getAdapter().state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.max_people_result_count_title),
|
||||
adapter = prefs2.maxPeopleResultCount.getAdapter(),
|
||||
step = 1,
|
||||
valueRange = 3..15,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
adapter = prefs.searchResultSettingsEntry.getAdapter(),
|
||||
label = stringResource(id = R.string.search_pref_result_settings_entry_title),
|
||||
)
|
||||
ExpandAndShrink(visible = prefs.searchResultSettingsEntry.getAdapter().state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.max_settings_entry_result_count_title),
|
||||
adapter = prefs2.maxSettingsEntryResultCount.getAdapter(),
|
||||
step = 1,
|
||||
valueRange = 2..10,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExpandAndShrink(visible = showDrawerSearchBar.state.value) {
|
||||
PreferenceGroup {
|
||||
DividerColumn {
|
||||
SwitchPreference(
|
||||
adapter = prefs.searchResultStartPageSuggestion.getAdapter(),
|
||||
label = stringResource(id = R.string.pref_suggestion_title),
|
||||
)
|
||||
ExpandAndShrink(visible = prefs.searchResultStartPageSuggestion.getAdapter().state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.max_suggestion_result_count_title),
|
||||
adapter = prefs2.maxSuggestionResultCount.getAdapter(),
|
||||
step = 1,
|
||||
valueRange = 3..10,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
adapter = prefs.searchResulRecentSuggestion.getAdapter(),
|
||||
label = stringResource(id = R.string.pref_recent_suggestion_title),
|
||||
)
|
||||
|
||||
ExpandAndShrink(visible = prefs.searchResulRecentSuggestion.getAdapter().state.value) {
|
||||
SliderPreference(
|
||||
label = stringResource(id = R.string.max_recent_result_count_title),
|
||||
adapter = prefs2.maxRecentResultCount.getAdapter(),
|
||||
step = 1,
|
||||
valueRange = 1..10,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deviceSearchEnabled) {
|
||||
ExpandAndShrink(visible = showDrawerSearchBar.state.value) {
|
||||
PreferenceGroup(heading = stringResource(id = R.string.show_search_result_types)) {
|
||||
|
||||
@@ -45,6 +45,11 @@ fun ExperimentalFeaturesPreferences() {
|
||||
adapter = prefs2.smartspaceModeSelection.getAdapter(),
|
||||
label = stringResource(id = R.string.smartspace_mode_selection),
|
||||
)
|
||||
SwitchPreference(
|
||||
adapter = prefs.performWideSearchExperimental.getAdapter(),
|
||||
label = stringResource(id = R.string.perform_wide_search_title),
|
||||
description = stringResource(id = R.string.perform_wide_search_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
|
||||
class AppInfoHelper(private val context: Context) {
|
||||
|
||||
fun getDefaultPhoneAppInfo(): AppInfo? {
|
||||
val phoneIntent = Intent(Intent.ACTION_DIAL)
|
||||
return getDefaultAppInfo(phoneIntent)
|
||||
}
|
||||
|
||||
fun getDefaultMessageAppInfo(): AppInfo? {
|
||||
val messageIntent = Intent(Intent.ACTION_SENDTO)
|
||||
messageIntent.data = android.net.Uri.parse("smsto:")
|
||||
return getDefaultAppInfo(messageIntent)
|
||||
}
|
||||
|
||||
private fun getDefaultAppInfo(intent: Intent): AppInfo? {
|
||||
val packageManager: PackageManager = context.packageManager
|
||||
val resolveInfo: ResolveInfo? = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
|
||||
return if (resolveInfo != null) {
|
||||
val appName: String = resolveInfo.loadLabel(packageManager).toString()
|
||||
val appIcon: Drawable = resolveInfo.loadIcon(packageManager)
|
||||
val packageName: String = resolveInfo.activityInfo.packageName
|
||||
AppInfo(appName, appIcon, packageName)
|
||||
} else {
|
||||
Log.e("AppInfoHelper", "No default app found for the given intent.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class AppInfo(val appName: String, val appIcon: Drawable, val packageName: String)
|
||||
@@ -1,9 +1,34 @@
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import app.lawnchair.theme.color.generateColor
|
||||
|
||||
fun GradientDrawable.getCornerRadiiCompat(): FloatArray? = try {
|
||||
cornerRadii
|
||||
} catch (_: NullPointerException) {
|
||||
null
|
||||
}
|
||||
|
||||
fun createTextBitmap(context: Context, text: String): Bitmap {
|
||||
val iconSize = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
||||
val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
// Set up paint for drawing text
|
||||
val paint = Paint().apply {
|
||||
color = generateColor(context)
|
||||
textSize = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size).toFloat()
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
// Draw text in the center of the bitmap
|
||||
val x = (bitmap.width / 2).toFloat()
|
||||
val y = (bitmap.height / 2 - (paint.descent() + paint.ascent()) / 2)
|
||||
canvas.drawText(text, x, y, paint)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import app.lawnchair.LawnchairApp
|
||||
import java.io.File
|
||||
import okio.FileMetadata
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
|
||||
internal fun Path.list(
|
||||
fileSystem: FileSystem = FileSystem.SYSTEM,
|
||||
isShowHidden: Boolean = false,
|
||||
isRecursively: Boolean = false,
|
||||
): Sequence<Path> {
|
||||
return runCatching {
|
||||
if (isRecursively) {
|
||||
fileSystem.listRecursively(this)
|
||||
} else {
|
||||
fileSystem.list(this).asSequence()
|
||||
}
|
||||
}.getOrDefault(emptySequence()).filter {
|
||||
if (isShowHidden) true else !it.isHidden
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Path.getMetadata(fileSystem: FileSystem = FileSystem.SYSTEM): FileMetadata? =
|
||||
fileSystem.metadataOrNull(this)
|
||||
|
||||
internal fun Path.isDirectory(fileSystem: FileSystem = FileSystem.SYSTEM): Boolean =
|
||||
getMetadata(fileSystem)?.isDirectory == true
|
||||
|
||||
internal fun Path.isFile(fileSystem: FileSystem = FileSystem.SYSTEM): Boolean =
|
||||
!isDirectory(fileSystem)
|
||||
|
||||
internal fun Path.sizeOrEmpty(fileSystem: FileSystem = FileSystem.SYSTEM): Long =
|
||||
getMetadata(fileSystem)?.size ?: 0
|
||||
|
||||
internal fun Path.isRegularFile(fileSystem: FileSystem = FileSystem.SYSTEM): Boolean =
|
||||
getMetadata(fileSystem)?.isRegularFile == true
|
||||
|
||||
internal val Path.isHidden: Boolean get() = toString().contains("/.")
|
||||
|
||||
internal val Path.exists: Boolean get() = toFile().exists()
|
||||
|
||||
internal val Path.extension: String?
|
||||
get() {
|
||||
val dotIndex = name.lastIndexOf(".")
|
||||
return if (dotIndex == -1) null else name.substring(dotIndex + 1)
|
||||
}
|
||||
|
||||
internal val Path.nameWithoutExtension: String get() = name.substringBeforeLast(".")
|
||||
|
||||
internal val Path.mimeType: String? get() = extension?.extension2MimeType()
|
||||
|
||||
val fileProviderAuthority: String = "${LawnchairApp.instance.packageName}.fileprovider"
|
||||
|
||||
fun String.path2Uri(): Uri? = File(this).file2Uri()
|
||||
|
||||
fun File.file2Uri(): Uri? = try {
|
||||
FileProvider.getUriForFile(LawnchairApp.instance, fileProviderAuthority, this)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageView
|
||||
import app.lawnchair.theme.color.ColorTokens
|
||||
import com.android.launcher3.R
|
||||
|
||||
class ImageViewWrapper(context: Context, attrs: AttributeSet?) : ImageView(context, attrs) {
|
||||
|
||||
private val mRadius = resources.getDimensionPixelSize(R.dimen.search_row_preview_radius).toFloat()
|
||||
private val path = Path()
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val rect = RectF()
|
||||
|
||||
init {
|
||||
scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
rect.set(0f, 0f, width.toFloat(), height.toFloat())
|
||||
val corners = floatArrayOf(
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
mRadius,
|
||||
)
|
||||
path.addRoundRect(rect, corners, Path.Direction.CW)
|
||||
|
||||
paint.color = ColorTokens.GroupHighlight.resolveColor(context)
|
||||
canvas.drawPath(path, paint)
|
||||
canvas.clipPath(path)
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.snebula.android.file.util
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.webkit.MimeTypeMap
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package app.lawnchair.util
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
|
||||
private const val TAG: String = "PackagePermissionManager"
|
||||
|
||||
fun requestContactPermissionGranted(context: Context, prefs: PreferenceManager): Boolean {
|
||||
val isGranted = contactPermissionGranted(context)
|
||||
if (!isGranted) {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri: Uri = Uri.fromParts("package", context.packageName, null)
|
||||
intent.data = uri
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
Log.e(TAG, "No activity found to handle application details settings intent")
|
||||
}
|
||||
prefs.searchResultPeople.set(false)
|
||||
}
|
||||
return isGranted
|
||||
}
|
||||
|
||||
fun contactPermissionGranted(context: Context): Boolean {
|
||||
return context.checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
fun checkAndRequestFilesPermission(context: Context, prefs: PreferenceManager): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
requestManageAllFilesAccessPermission(context)
|
||||
prefs.searchResultFiles.set(false)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!hasReadExternalStoragePermission(context)) {
|
||||
requestReadExternalStoragePermission(context)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun filesAndStorageGranted(context: Context): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Environment.isExternalStorageManager() else hasReadExternalStoragePermission(context)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun requestManageAllFilesAccessPermission(context: Context) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
intent.data = Uri.fromParts("package", context.packageName, null)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun hasReadExternalStoragePermission(context: Context): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun requestReadExternalStoragePermission(context: Context) {
|
||||
ActivityCompat.requestPermissions(
|
||||
context as Activity,
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||
123,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user