Skip to content

Commit 0353568

Browse files
committed
Merge branch 'feature/compose-migration-and-new-features' into develop
2 parents 7634627 + 75bd6be commit 0353568

27 files changed

Lines changed: 658 additions & 163 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@
1414
.cxx
1515

1616
.idea/
17+
keystore/
18+
commandlinetools-linux-*.zip
19+
android-sdk/

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
# DeepLink-test-Android
2-
3-
1wGET2)vX[k5z>k(enck
1+
# DeepLink Test Android

app/build.gradle

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

app/build.gradle.kts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.kotlin.compose)
5+
alias(libs.plugins.kotlin.kapt)
6+
alias(libs.plugins.kotlin.parcelize)
7+
}
8+
9+
android {
10+
namespace = "com.simdea.deeplinktester"
11+
compileSdk = 36
12+
13+
defaultConfig {
14+
applicationId = "com.simdea.deeplinktester"
15+
minSdk = 27
16+
targetSdk = 36
17+
versionCode = 1
18+
versionName = "1.0"
19+
20+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21+
}
22+
23+
buildTypes {
24+
release {
25+
isMinifyEnabled = false
26+
proguardFiles(
27+
getDefaultProguardFile("proguard-android-optimize.txt"),
28+
"proguard-rules.pro"
29+
)
30+
}
31+
}
32+
compileOptions {
33+
sourceCompatibility = JavaVersion.VERSION_11
34+
targetCompatibility = JavaVersion.VERSION_11
35+
}
36+
kotlinOptions {
37+
jvmTarget = "11"
38+
}
39+
buildFeatures {
40+
compose = true
41+
}
42+
}
43+
44+
dependencies {
45+
implementation(libs.play.services.ads)
46+
implementation(libs.androidx.room.runtime)
47+
implementation(libs.androidx.room.ktx)
48+
kapt(libs.androidx.room.compiler)
49+
implementation(libs.androidx.core.ktx)
50+
implementation(libs.androidx.lifecycle.runtime.ktx)
51+
implementation(libs.androidx.lifecycle.viewmodel.ktx)
52+
implementation(libs.androidx.activity.compose)
53+
implementation(libs.androidx.navigation.compose)
54+
implementation(platform(libs.androidx.compose.bom))
55+
implementation(libs.androidx.compose.ui)
56+
implementation(libs.androidx.compose.ui.graphics)
57+
implementation(libs.androidx.compose.ui.tooling.preview)
58+
implementation(libs.androidx.compose.material3)
59+
implementation(libs.androidx.compose.material.icons.extended)
60+
testImplementation(libs.junit)
61+
androidTestImplementation(libs.androidx.junit)
62+
androidTestImplementation(libs.androidx.espresso.core)
63+
androidTestImplementation(platform(libs.androidx.compose.bom))
64+
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
65+
debugImplementation(libs.androidx.compose.ui.tooling)
66+
debugImplementation(libs.androidx.compose.ui.test.manifest)
67+
}

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@
66
android:icon="@mipmap/ic_launcher"
77
android:label="@string/app_name"
88
android:roundIcon="@mipmap/ic_launcher_round"
9-
android:supportsRtl="true"
10-
android:theme="@style/AppTheme">
11-
<activity android:exported="true" android:name=".MainActivity">
9+
android:supportsRtl="true">
10+
<activity
11+
android:exported="true"
12+
android:name=".MainActivity"
13+
android:windowSoftInputMode="adjustResize"
14+
android:theme="@style/Theme.DeepLinkTestAndroid">
1215
<intent-filter>
1316
<action android:name="android.intent.action.MAIN"/>
1417

1518
<category android:name="android.intent.category.LAUNCHER"/>
1619
</intent-filter>
1720
</activity>
21+
22+
<meta-data
23+
android:name="com.google.android.gms.ads.APPLICATION_ID"
24+
android:value="ca-app-pub-3940256099942544~3347511713"/>
1825
</application>
1926

2027
</manifest>
Lines changed: 174 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,194 @@
11
package com.simdea.deeplinktester
22

3+
import android.app.Application
4+
import android.content.ActivityNotFoundException
35
import android.content.Intent
46
import android.net.Uri
57
import android.os.Bundle
6-
import androidx.appcompat.app.AppCompatActivity
7-
import androidx.databinding.DataBindingUtil
8-
import com.simdea.deeplinktester.databinding.ActivityMainBinding
8+
import androidx.activity.ComponentActivity
9+
import androidx.activity.compose.setContent
10+
import androidx.compose.foundation.layout.*
11+
import androidx.compose.material.icons.Icons
12+
import androidx.compose.material.icons.filled.History
13+
import androidx.compose.material.icons.filled.Home
14+
import androidx.compose.material3.*
15+
import androidx.compose.runtime.*
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.ExperimentalComposeUiApi
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.platform.LocalContext
20+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
21+
import androidx.compose.foundation.text.KeyboardActions
22+
import androidx.compose.foundation.text.KeyboardOptions
23+
import androidx.compose.ui.text.input.ImeAction
24+
import androidx.compose.ui.res.stringResource
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import androidx.compose.ui.unit.dp
27+
import androidx.lifecycle.viewmodel.compose.viewModel
28+
import androidx.navigation.NavDestination.Companion.hierarchy
29+
import androidx.navigation.NavGraph.Companion.findStartDestination
30+
import androidx.navigation.compose.NavHost
31+
import androidx.navigation.compose.composable
32+
import androidx.navigation.compose.currentBackStackEntryAsState
33+
import androidx.navigation.compose.rememberNavController
34+
import com.simdea.deeplinktester.data.Deeplink
35+
import com.simdea.deeplinktester.ui.ads.BannerAd
36+
import com.simdea.deeplinktester.ui.history.HistoryScreen
37+
import com.simdea.deeplinktester.ui.history.HistoryViewModel
38+
import com.simdea.deeplinktester.ui.theme.DeepLinkTestAndroidTheme
39+
import kotlinx.coroutines.launch
940

10-
class MainActivity : AppCompatActivity() {
41+
sealed class Screen(val route: String, val resourceId: Int, val icon: @Composable () -> Unit) {
42+
object Main : Screen("main", R.string.main_screen_title, { Icon(Icons.Filled.Home, contentDescription = null) })
43+
object History : Screen("history", R.string.history_screen_title, { Icon(Icons.Filled.History, contentDescription = null) })
44+
}
1145

12-
private val dataBinding: ActivityMainBinding by lazy {
13-
DataBindingUtil.setContentView(this, R.layout.activity_main)
14-
}
46+
val items = listOf(
47+
Screen.Main,
48+
Screen.History
49+
)
1550

51+
class MainActivity : ComponentActivity() {
1652
override fun onCreate(savedInstanceState: Bundle?) {
1753
super.onCreate(savedInstanceState)
54+
setContent {
55+
DeepLinkTestAndroidTheme {
56+
AppNavigation()
57+
}
58+
}
59+
}
60+
}
1861

19-
dataBinding.lifecycleOwner = this
62+
@OptIn(ExperimentalMaterial3Api::class)
63+
@Composable
64+
fun AppNavigation() {
65+
val navController = rememberNavController()
66+
val context = LocalContext.current
67+
val historyViewModel: HistoryViewModel = viewModel(
68+
factory = HistoryViewModel.HistoryViewModelFactory(
69+
context.applicationContext as Application
70+
)
71+
)
72+
val snackbarHostState = remember { SnackbarHostState() }
73+
val scope = rememberCoroutineScope()
2074

21-
dataBinding.btnSubmit.setOnClickListener {
22-
launchBrowser(dataBinding.edtDL.text.toString())
75+
Scaffold(
76+
snackbarHost = { SnackbarHost(snackbarHostState) },
77+
bottomBar = {
78+
Column {
79+
BannerAd()
80+
NavigationBar {
81+
val navBackStackEntry by navController.currentBackStackEntryAsState()
82+
val currentDestination = navBackStackEntry?.destination
83+
items.forEach { screen ->
84+
NavigationBarItem(
85+
icon = { screen.icon() },
86+
label = { Text(stringResource(screen.resourceId)) },
87+
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
88+
onClick = {
89+
navController.navigate(screen.route) {
90+
popUpTo(navController.graph.findStartDestination().id) {
91+
saveState = true
92+
}
93+
launchSingleTop = true
94+
restoreState = true
95+
}
96+
}
97+
)
98+
}
99+
}
100+
}
101+
}
102+
) { innerPadding ->
103+
NavHost(navController, startDestination = Screen.Main.route, Modifier.padding(innerPadding)) {
104+
composable(Screen.Main.route) {
105+
MainScreen(
106+
onLaunch = { deeplink ->
107+
historyViewModel.addDeeplink(deeplink)
108+
try {
109+
val launchBrowser = Intent(Intent.ACTION_VIEW).apply {
110+
data = Uri.parse(deeplink.deeplink)
111+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
112+
}
113+
context.startActivity(launchBrowser)
114+
} catch (e: ActivityNotFoundException) {
115+
scope.launch {
116+
snackbarHostState.showSnackbar(
117+
message = context.getString(R.string.activity_not_found_error),
118+
duration = SnackbarDuration.Short
119+
)
120+
}
121+
}
122+
}
123+
)
124+
}
125+
composable(Screen.History.route) {
126+
val history by historyViewModel.history.collectAsState()
127+
HistoryScreen(
128+
history = history,
129+
onRetry = { deeplink ->
130+
try {
131+
val launchBrowser = Intent(Intent.ACTION_VIEW).apply {
132+
data = Uri.parse(deeplink.deeplink)
133+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
134+
}
135+
context.startActivity(launchBrowser)
136+
} catch (e: ActivityNotFoundException) {
137+
scope.launch {
138+
snackbarHostState.showSnackbar(
139+
message = context.getString(R.string.activity_not_found_error),
140+
duration = SnackbarDuration.Short
141+
)
142+
}
143+
}
144+
},
145+
onRemove = { historyViewModel.removeDeeplink(it) }
146+
)
147+
}
23148
}
149+
}
150+
}
151+
152+
@OptIn(ExperimentalComposeUiApi::class)
153+
@Composable
154+
fun MainScreen(
155+
onLaunch: (Deeplink) -> Unit
156+
) {
157+
var text by remember { mutableStateOf("") }
158+
val keyboardController = LocalSoftwareKeyboardController.current
24159

160+
val submit = {
161+
if (text.isNotBlank()) {
162+
onLaunch(Deeplink(deeplink = text))
163+
keyboardController?.hide()
164+
}
25165
}
26166

27-
private fun launchBrowser(action: String) {
28-
val launchBrowser = Intent(Intent.ACTION_VIEW).apply {
29-
data = Uri.parse(action)
30-
flags = Intent.FLAG_ACTIVITY_NEW_TASK
167+
Column(
168+
modifier = Modifier.fillMaxSize(),
169+
verticalArrangement = Arrangement.Center,
170+
horizontalAlignment = Alignment.CenterHorizontally
171+
) {
172+
OutlinedTextField(
173+
value = text,
174+
onValueChange = { text = it },
175+
label = { Text(stringResource(R.string.deeplink_label)) },
176+
modifier = Modifier.width(300.dp),
177+
singleLine = true,
178+
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
179+
keyboardActions = KeyboardActions(onDone = { submit() })
180+
)
181+
Spacer(modifier = Modifier.height(16.dp))
182+
Button(onClick = submit) {
183+
Text(stringResource(R.string.open_deeplink_button))
31184
}
32-
startActivity(launchBrowser)
33185
}
186+
}
34187

188+
@Preview(showBackground = true)
189+
@Composable
190+
fun DefaultPreview() {
191+
DeepLinkTestAndroidTheme {
192+
MainScreen(onLaunch = {})
193+
}
35194
}

0 commit comments

Comments
 (0)