Skip to content

Commit 1df5bca

Browse files
committed
feat: implement MotionLayout based app drawer and home screen navigation
1 parent 445f120 commit 1df5bca

28 files changed

Lines changed: 327 additions & 367 deletions

app/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ android {
4141
targetCompatibility = JavaVersion.VERSION_11
4242
}
4343
buildFeatures {
44-
compose = true
44+
buildConfig = true
4545
}
4646
}
4747

@@ -55,6 +55,7 @@ dependencies {
5555
implementation(libs.androidx.compose.ui.tooling.preview)
5656
implementation(libs.androidx.compose.material3)
5757
implementation(libs.androidx.recyclerview)
58+
implementation(libs.material)
5859
testImplementation(libs.junit)
5960
androidTestImplementation(libs.androidx.junit)
6061
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5-
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
5+
<uses-permission
6+
android:name="android.permission.QUERY_ALL_PACKAGES"
67
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
78

89
<application
@@ -17,16 +18,21 @@
1718
<activity
1819
android:name=".MainActivity"
1920
android:exported="true"
20-
android:label="@string/app_name"
21-
android:theme="@style/Theme.WebviumLauncher"
22-
android:launchMode="singleTask">
21+
android:launchMode="singleTask"
22+
android:theme="@style/Theme.WebviumLauncher">
2323
<intent-filter>
2424
<action android:name="android.intent.action.MAIN" />
25+
2526
<category android:name="android.intent.category.HOME" />
2627
<category android:name="android.intent.category.DEFAULT" />
2728
<category android:name="android.intent.category.LAUNCHER" />
2829
</intent-filter>
2930
</activity>
31+
32+
<activity
33+
android:name=".WebviumLauncherActivity"
34+
android:launchMode="singleTask"
35+
android:theme="@style/Theme.WebviumLauncher" />
3036
</application>
3137

3238
</manifest>
12 KB
Loading
Lines changed: 3 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,16 @@
11
package com.mrepol742.webviumlauncher
22

33
import android.content.Intent
4-
import android.graphics.Color
5-
import android.graphics.RenderEffect
6-
import android.graphics.Shader
7-
import android.os.Build
84
import android.os.Bundle
9-
import android.view.View
10-
import android.widget.SearchView
115
import androidx.activity.ComponentActivity
12-
import androidx.activity.compose.setContent
13-
import androidx.activity.enableEdgeToEdge
14-
import androidx.compose.foundation.layout.fillMaxSize
15-
import androidx.compose.foundation.layout.padding
16-
import androidx.compose.material3.Scaffold
17-
import androidx.compose.material3.Text
18-
import androidx.compose.runtime.Composable
19-
import androidx.compose.ui.Modifier
20-
import androidx.compose.ui.tooling.preview.Preview
21-
import androidx.core.view.ViewCompat
22-
import androidx.core.view.WindowCompat
23-
import androidx.core.view.WindowInsetsCompat
24-
import androidx.recyclerview.widget.GridLayoutManager
25-
import androidx.recyclerview.widget.RecyclerView
26-
import com.mrepol742.webviumlauncher.ui.theme.WebviumLauncherTheme
276

287
class MainActivity : ComponentActivity() {
298

30-
private lateinit var recyclerView: RecyclerView
31-
private lateinit var searchView: SearchView
32-
private lateinit var adapter: AppAdapter
33-
34-
private val allApps = mutableListOf<AppInfo>()
35-
private val filteredApps = mutableListOf<AppInfo>()
36-
379
override fun onCreate(savedInstanceState: Bundle?) {
3810
super.onCreate(savedInstanceState)
39-
enableEdgeToEdge();
40-
setContentView(R.layout.activity_main)
41-
42-
val root = findViewById<View>(R.id.root)
43-
val blurLayer = findViewById<View>(R.id.blurLayer)
44-
recyclerView = findViewById(R.id.recyclerView)
45-
searchView = findViewById(R.id.searchView)
46-
47-
recyclerView.layoutManager = GridLayoutManager(this, 4)
48-
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
49-
val statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars())
50-
51-
view.setPadding(
52-
view.paddingLeft,
53-
statusBarInsets.top,
54-
view.paddingRight,
55-
view.paddingBottom
56-
)
57-
58-
insets
59-
}
60-
61-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
62-
blurLayer.setRenderEffect(
63-
RenderEffect.createBlurEffect(
64-
30f, // radius X
65-
30f, // radius Y
66-
Shader.TileMode.CLAMP
67-
)
68-
)
69-
}
70-
71-
adapter = AppAdapter(filteredApps) { app ->
72-
startActivity(app.launchIntent)
73-
}
74-
75-
recyclerView.adapter = adapter
76-
setupSearch()
77-
loadApps()
78-
}
79-
80-
81-
private fun loadApps() {
82-
val pm = packageManager
83-
val intent = Intent(Intent.ACTION_MAIN, null)
84-
intent.addCategory(Intent.CATEGORY_LAUNCHER)
85-
86-
val resolveInfos = pm.queryIntentActivities(intent, 0)
87-
88-
allApps.clear()
89-
for (info in resolveInfos) {
90-
val label = info.loadLabel(pm).toString()
91-
val packageName = info.activityInfo.packageName
92-
val icon = info.loadIcon(pm)
93-
val launchIntent = pm.getLaunchIntentForPackage(packageName) ?: continue
94-
95-
96-
allApps.add(AppInfo(label, packageName, icon, launchIntent))
97-
}
98-
99-
allApps.sortBy { it.label.lowercase() }
100-
filteredApps.clear()
101-
filteredApps.addAll(allApps)
102-
adapter.notifyDataSetChanged()
103-
}
104-
105-
private fun setupSearch() {
106-
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
107-
override fun onQueryTextSubmit(query: String?): Boolean = false
108-
109-
110-
override fun onQueryTextChange(newText: String?): Boolean {
111-
val q = newText.orEmpty().lowercase()
112-
filteredApps.clear()
113-
114-
115-
if (q.isEmpty()) {
116-
filteredApps.addAll(allApps)
117-
} else {
118-
filteredApps.addAll(allApps.filter {
119-
it.label.lowercase().contains(q)
120-
})
121-
}
122-
11+
val intent = Intent(this, WebviumLauncherActivity::class.java)
12+
startActivity(intent)
12313

124-
adapter.notifyDataSetChanged()
125-
return true
126-
}
127-
})
14+
finish()
12815
}
12916
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.mrepol742.webviumlauncher
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import android.view.View
7+
import android.widget.SearchView
8+
import androidx.activity.ComponentActivity
9+
import androidx.constraintlayout.motion.widget.MotionLayout
10+
import androidx.recyclerview.widget.GridLayoutManager
11+
import androidx.recyclerview.widget.RecyclerView
12+
13+
class WebviumLauncherActivity : ComponentActivity() {
14+
15+
private lateinit var appDrawerRecyclerView: RecyclerView
16+
private lateinit var appDrawerSearchView: SearchView
17+
private lateinit var homeRecyclerView: RecyclerView
18+
private lateinit var adapter: AppAdapter
19+
20+
private val allApps = mutableListOf<AppInfo>()
21+
private val filteredApps = mutableListOf<AppInfo>()
22+
private val homeShortcuts = mutableListOf<AppInfo>()
23+
24+
override fun onCreate(savedInstanceState: Bundle?) {
25+
super.onCreate(savedInstanceState)
26+
/* enableEdgeToEdge();*/
27+
setContentView(R.layout.activity_webvium_launcher)
28+
29+
appDrawerRecyclerView = findViewById(R.id.appDrawerRecyclerView)
30+
appDrawerSearchView = findViewById(R.id.appDrawerSearchView)
31+
homeRecyclerView = findViewById(R.id.homeRecyclerView)
32+
33+
initHome();
34+
initAppDrawer();
35+
initSearch()
36+
loadApps()
37+
}
38+
39+
@SuppressLint("ClickableViewAccessibility")
40+
private fun initHome() {
41+
homeRecyclerView.setOnTouchListener { v, event ->
42+
v.parent.requestDisallowInterceptTouchEvent(true)
43+
false
44+
}
45+
46+
homeRecyclerView.layoutManager = GridLayoutManager(this, 4)
47+
48+
homeRecyclerView.adapter = AppAdapter(filteredApps) { app ->
49+
startActivity(app.launchIntent)
50+
}
51+
}
52+
53+
@SuppressLint("ClickableViewAccessibility")
54+
private fun initAppDrawer() {
55+
val blurLayer = findViewById<View>(R.id.blurLayer)
56+
val appDrawer = findViewById<View>(R.id.appDrawer)
57+
val motionLayout = findViewById<MotionLayout>(R.id.motionLayout)
58+
if (BuildConfig.DEBUG) motionLayout.setDebugMode(MotionLayout.DEBUG_SHOW_PROGRESS)
59+
60+
appDrawerRecyclerView.setOnTouchListener { v, event ->
61+
v.parent.requestDisallowInterceptTouchEvent(true)
62+
false
63+
}
64+
65+
appDrawerRecyclerView.layoutManager = GridLayoutManager(this, 4)
66+
/*ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
67+
val statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars())
68+
69+
view.setPadding(
70+
view.paddingLeft,
71+
statusBarInsets.top,
72+
view.paddingRight,
73+
view.paddingBottom
74+
)
75+
76+
insets
77+
}
78+
79+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
80+
blurLayer.setRenderEffect(
81+
RenderEffect.createBlurEffect(
82+
30f, // radius X
83+
30f, // radius Y
84+
Shader.TileMode.CLAMP
85+
)
86+
)
87+
}*/
88+
89+
adapter = AppAdapter(filteredApps) { app ->
90+
if (appDrawer.alpha == 1f) {
91+
startActivity(app.launchIntent)
92+
}
93+
}
94+
95+
appDrawerRecyclerView.adapter = adapter
96+
}
97+
98+
private fun loadApps() {
99+
val pm = packageManager
100+
val intent = Intent(Intent.ACTION_MAIN, null)
101+
intent.addCategory(Intent.CATEGORY_LAUNCHER)
102+
103+
val resolveInfos = pm.queryIntentActivities(intent, 0)
104+
105+
allApps.clear()
106+
for (info in resolveInfos) {
107+
val label = info.loadLabel(pm).toString()
108+
val packageName = info.activityInfo.packageName
109+
val icon = info.loadIcon(pm)
110+
val launchIntent = pm.getLaunchIntentForPackage(packageName) ?: continue
111+
112+
113+
allApps.add(AppInfo(label, packageName, icon, launchIntent))
114+
}
115+
116+
allApps.sortBy { it.label.lowercase() }
117+
filteredApps.clear()
118+
filteredApps.addAll(allApps)
119+
adapter.notifyDataSetChanged()
120+
}
121+
122+
private fun initSearch() {
123+
appDrawerSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
124+
override fun onQueryTextSubmit(query: String?): Boolean = false
125+
126+
127+
override fun onQueryTextChange(newText: String?): Boolean {
128+
val q = newText.orEmpty().lowercase()
129+
filteredApps.clear()
130+
131+
132+
if (q.isEmpty()) {
133+
filteredApps.addAll(allApps)
134+
} else {
135+
filteredApps.addAll(allApps.filter {
136+
it.label.lowercase().contains(q)
137+
})
138+
}
139+
140+
141+
adapter.notifyDataSetChanged()
142+
return true
143+
}
144+
})
145+
}
146+
}

app/src/main/res/drawable-v24/ic_launcher_foreground.xml

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)