Skip to content

Commit 445f120

Browse files
committed
feat: implement basic launcher functionality
1 parent f931c16 commit 445f120

12 files changed

Lines changed: 296 additions & 35 deletions

File tree

.idea/codeStyles/Project.xml

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,26 @@ android {
1414
minSdk = 21
1515
targetSdk = 36
1616
versionCode = 1
17-
versionName = "1.0"
17+
versionName = "1.0.0"
1818

1919
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20+
proguardFiles(
21+
getDefaultProguardFile("proguard-android-optimize.txt"),
22+
"proguard-rules.pro"
23+
)
2024
}
2125

2226
buildTypes {
23-
release {
27+
debug {
28+
isDebuggable = true
2429
isMinifyEnabled = false
25-
proguardFiles(
26-
getDefaultProguardFile("proguard-android-optimize.txt"),
27-
"proguard-rules.pro"
28-
)
30+
applicationIdSuffix = ".debug"
31+
versionNameSuffix = "-debug"
32+
}
33+
34+
release {
35+
isDebuggable = false
36+
isMinifyEnabled = true
2937
}
3038
}
3139
compileOptions {
@@ -46,6 +54,7 @@ dependencies {
4654
implementation(libs.androidx.compose.ui.graphics)
4755
implementation(libs.androidx.compose.ui.tooling.preview)
4856
implementation(libs.androidx.compose.material3)
57+
implementation(libs.androidx.recyclerview)
4958
testImplementation(libs.junit)
5059
androidTestImplementation(libs.androidx.junit)
5160
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/AndroidManifest.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
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"
6+
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
7+
58
<application
69
android:allowBackup="true"
710
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -15,10 +18,12 @@
1518
android:name=".MainActivity"
1619
android:exported="true"
1720
android:label="@string/app_name"
18-
android:theme="@style/Theme.WebviumLauncher">
21+
android:theme="@style/Theme.WebviumLauncher"
22+
android:launchMode="singleTask">
1923
<intent-filter>
2024
<action android:name="android.intent.action.MAIN" />
21-
25+
<category android:name="android.intent.category.HOME" />
26+
<category android:name="android.intent.category.DEFAULT" />
2227
<category android:name="android.intent.category.LAUNCHER" />
2328
</intent-filter>
2429
</activity>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.mrepol742.webviumlauncher
2+
3+
import android.content.Intent
4+
import android.net.Uri
5+
import android.provider.Settings
6+
import android.view.LayoutInflater
7+
import android.view.View
8+
import android.view.ViewGroup
9+
import android.widget.ImageView
10+
import android.widget.TextView
11+
import androidx.recyclerview.widget.RecyclerView
12+
import androidx.core.net.toUri
13+
14+
class AppAdapter(
15+
private val apps: List<AppInfo>,
16+
private val onClick: (AppInfo) -> Unit
17+
) : RecyclerView.Adapter<AppAdapter.ViewHolder>() {
18+
19+
20+
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
21+
val icon: ImageView = view.findViewById(R.id.icon)
22+
val label: TextView = view.findViewById(R.id.label)
23+
}
24+
25+
26+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
27+
val view = LayoutInflater.from(parent.context)
28+
.inflate(R.layout.item_app, parent, false)
29+
return ViewHolder(view)
30+
}
31+
32+
33+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
34+
val app = apps[position]
35+
holder.icon.setImageDrawable(app.icon)
36+
holder.label.text = app.label
37+
38+
39+
holder.itemView.setOnClickListener {
40+
onClick(app)
41+
}
42+
43+
44+
holder.itemView.setOnLongClickListener {
45+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
46+
intent.data = "package:${app.packageName}".toUri()
47+
holder.itemView.context.startActivity(intent)
48+
true
49+
}
50+
}
51+
52+
53+
override fun getItemCount() = apps.size
54+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.mrepol742.webviumlauncher
2+
3+
import android.content.Intent
4+
import android.graphics.drawable.Drawable
5+
6+
data class AppInfo(
7+
val label: String,
8+
val packageName: String,
9+
val icon: Drawable,
10+
val launchIntent: Intent
11+
)
Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package com.mrepol742.webviumlauncher
22

3+
import android.content.Intent
4+
import android.graphics.Color
5+
import android.graphics.RenderEffect
6+
import android.graphics.Shader
7+
import android.os.Build
38
import android.os.Bundle
9+
import android.view.View
10+
import android.widget.SearchView
411
import androidx.activity.ComponentActivity
512
import androidx.activity.compose.setContent
613
import androidx.activity.enableEdgeToEdge
@@ -11,37 +18,112 @@ import androidx.compose.material3.Text
1118
import androidx.compose.runtime.Composable
1219
import androidx.compose.ui.Modifier
1320
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
1426
import com.mrepol742.webviumlauncher.ui.theme.WebviumLauncherTheme
1527

1628
class MainActivity : ComponentActivity() {
29+
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+
1737
override fun onCreate(savedInstanceState: Bundle?) {
1838
super.onCreate(savedInstanceState)
19-
enableEdgeToEdge()
20-
setContent {
21-
WebviumLauncherTheme {
22-
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
23-
Greeting(
24-
name = "Android",
25-
modifier = Modifier.padding(innerPadding)
26-
)
27-
}
28-
}
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))
2997
}
98+
99+
allApps.sortBy { it.label.lowercase() }
100+
filteredApps.clear()
101+
filteredApps.addAll(allApps)
102+
adapter.notifyDataSetChanged()
30103
}
31-
}
32-
33-
@Composable
34-
fun Greeting(name: String, modifier: Modifier = Modifier) {
35-
Text(
36-
text = "Hello $name!",
37-
modifier = modifier
38-
)
39-
}
40-
41-
@Preview(showBackground = true)
42-
@Composable
43-
fun GreetingPreview() {
44-
WebviumLauncherTheme {
45-
Greeting("Android")
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+
123+
124+
adapter.notifyDataSetChanged()
125+
return true
126+
}
127+
})
46128
}
47129
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@+id/root"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent">
6+
7+
<View
8+
android:id="@+id/blurLayer"
9+
android:layout_width="match_parent"
10+
android:layout_height="match_parent"
11+
android:background="#80FFFFFF" />
12+
13+
<LinearLayout
14+
android:layout_width="match_parent"
15+
android:layout_height="match_parent"
16+
android:orientation="vertical">
17+
18+
<SearchView
19+
android:id="@+id/searchView"
20+
android:layout_width="match_parent"
21+
android:layout_height="wrap_content"
22+
android:iconifiedByDefault="false"
23+
android:queryHint="@string/search_apps" />
24+
25+
<androidx.recyclerview.widget.RecyclerView
26+
android:id="@+id/recyclerView"
27+
android:layout_width="match_parent"
28+
android:layout_height="0dp"
29+
android:layout_weight="1"
30+
android:clipToPadding="false"
31+
android:padding="8dp" />
32+
</LinearLayout>
33+
</FrameLayout>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
android:gravity="center"
6+
android:orientation="vertical"
7+
android:padding="8dp">
8+
9+
<ImageView
10+
android:id="@+id/icon"
11+
android:layout_width="48dp"
12+
android:layout_height="48dp" />
13+
14+
<TextView
15+
android:id="@+id/label"
16+
android:layout_width="wrap_content"
17+
android:layout_height="wrap_content"
18+
android:layout_marginTop="4dp"
19+
android:ellipsize="end"
20+
android:maxLines="1"
21+
android:textSize="12sp" />
22+
23+
24+
</LinearLayout>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<resources>
22
<string name="app_name">Webvium Launcher</string>
3+
<string name="search_apps">Search apps</string>
34
</resources>

0 commit comments

Comments
 (0)