Skip to content

Commit 5ea8fe8

Browse files
authored
Share articles on social media feature.
Now users can share articles on social media via link generated with the help of Firebase Dynamic Links.
1 parent a31c5a2 commit 5ea8fe8

File tree

41 files changed

+425
-197
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+425
-197
lines changed

app/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/build
1+
/build
2+
.google-services.json

app/build.gradle

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ android {
2323

2424
apply plugin: 'kotlin-kapt'
2525
apply plugin: 'dagger.hilt.android.plugin'
26+
apply plugin: 'com.google.gms.google-services'
2627

2728
def versionProperties = new Properties()
2829
versionProperties.load(new FileInputStream(rootProject.file("version.properties")))
2930

3031
def _versionCode = "${versionProperties['VERSION_CODE']}".toInteger()
3132
def _versionName =
3233
"${versionProperties['MAJOR']}" +
33-
".${versionProperties['VERSION_CODE']}" +
34-
".${versionProperties['MINOR']}" +
35-
".${versionProperties['PATCH']}"
34+
".${versionProperties['VERSION_CODE']}" +
35+
".${versionProperties['MINOR']}" +
36+
".${versionProperties['PATCH']}"
3637

3738
android {
3839
defaultConfig {
@@ -87,4 +88,6 @@ dependencies {
8788
implementation(project(":ui-article-detail"))
8889
implementation(project(":ui-subscribe-topic"))
8990
implementation(project(":ui-bookmarks"))
90-
}
91+
92+
implementation Firebase.dynamicLink
93+
}

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@
3737
android:scheme="https" />
3838
</intent-filter>
3939

40+
<intent-filter>
41+
<action android:name="android.intent.action.VIEW" />
42+
<category android:name="android.intent.category.DEFAULT" />
43+
<category android:name="android.intent.category.BROWSABLE" />
44+
45+
<!-- the host should contain your domain. The pattern should be same as it's mentioned without any special char -->
46+
47+
<data
48+
android:host="slimektopensource.page.link"
49+
android:scheme="https" />
50+
</intent-filter>
51+
4052
</activity>
4153

4254
<provider
@@ -64,4 +76,4 @@
6476

6577
</application>
6678

67-
</manifest>
79+
</manifest>

app/src/main/java/kasem/sm/slime/MainActivity.kt

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,25 @@
44
*/
55
package kasem.sm.slime
66

7+
import android.content.Intent
8+
import android.net.Uri
79
import android.os.Build.VERSION_CODES.S
810
import android.os.Bundle
911
import androidx.activity.ComponentActivity
1012
import androidx.activity.compose.setContent
1113
import androidx.annotation.RequiresApi
1214
import coil.ImageLoader
15+
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
16+
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
17+
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
18+
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
1319
import dagger.hilt.android.AndroidEntryPoint
1420
import javax.inject.Inject
21+
import kasem.sm.common_ui.util.Destination
1522
import kasem.sm.slime.ui.navigation.SlimeNavigation
23+
import timber.log.Timber
1624

25+
@OptIn(ExperimentalMaterialNavigationApi::class)
1726
@AndroidEntryPoint
1827
class MainActivity : ComponentActivity() {
1928

@@ -24,7 +33,55 @@ class MainActivity : ComponentActivity() {
2433
override fun onCreate(savedInstanceState: Bundle?) {
2534
super.onCreate(savedInstanceState)
2635
setContent {
27-
SlimeNavigation(imageLoader = imageLoader)
36+
37+
val bottomSheetNavigator = rememberBottomSheetNavigator()
38+
val navController = rememberAnimatedNavController(bottomSheetNavigator)
39+
40+
SlimeNavigation(
41+
navController = navController,
42+
imageLoader = imageLoader,
43+
bottomSheetNavigator = bottomSheetNavigator
44+
)
45+
46+
// Handle when app is opened via shareable link
47+
FirebaseDynamicLinks.getInstance()
48+
.getDynamicLink(intent)
49+
.addOnSuccessListener(this) { pendingDynamicLinkData ->
50+
var deepLink: Uri? = null
51+
52+
if (pendingDynamicLinkData != null) {
53+
deepLink = pendingDynamicLinkData.link
54+
}
55+
56+
deepLink?.let { link ->
57+
val path =
58+
link.toString().substring(link.toString().lastIndexOf("/") + 1)
59+
when {
60+
/**
61+
* Only execute if the sharing link contains 'article' in it as our
62+
* shareable link would look like https://slimektopensource.page.link/article/{articleId}
63+
*/
64+
link.toString().contains("article") -> {
65+
val articleId = try {
66+
path.toInt()
67+
} catch (e: Exception) {
68+
null
69+
}
70+
71+
articleId?.let { id ->
72+
navController.navigate(Destination.articleDetail(id))
73+
// Clear the id from Intent
74+
intent.apply {
75+
data = null
76+
Intent()
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}.addOnFailureListener {
83+
Timber.d(it.message)
84+
}
2885
}
2986
}
3087
}

app/src/main/java/kasem/sm/slime/ui/navigation/NavExtensions.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package kasem.sm.slime.ui.navigation
77
import androidx.compose.runtime.Composable
88
import androidx.navigation.NavController
99
import androidx.navigation.compose.currentBackStackEntryAsState
10-
import kasem.sm.common_ui.util.Routes
10+
import kasem.sm.common_ui.util.Destination
1111

1212
@Composable
1313
fun NavController.currentRouteAsState(): String? {
@@ -17,15 +17,15 @@ fun NavController.currentRouteAsState(): String? {
1717
@Composable
1818
fun NavController.isNotAuthRoute(): Boolean {
1919
return when (currentRouteAsState()) {
20-
Routes.HomeScreen.route -> true
21-
Routes.ExploreScreen().route -> true
22-
Routes.ProfileScreen.route -> true
23-
Routes.BookmarkScreen.route -> true
20+
Destination.HomeScreen.route -> true
21+
Destination.ExploreScreen().route -> true
22+
Destination.ProfileScreen.route -> true
23+
Destination.BookmarkScreen.route -> true
2424
else -> false
2525
}
2626
}
2727

2828
@Composable
2929
fun NavController.isProfileScreenRoute(): Boolean {
30-
return currentRouteAsState() == Routes.ProfileScreen.route
30+
return currentRouteAsState() == Destination.ProfileScreen.route
3131
}

app/src/main/java/kasem/sm/slime/ui/navigation/NavHost.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import com.google.accompanist.navigation.material.BottomSheetNavigator
1414
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
1515
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
1616
import kasem.sm.common_ui.InitSlimeSystemUI
17-
import kasem.sm.common_ui.util.Routes
18-
import kasem.sm.common_ui.util.Routes.Main
17+
import kasem.sm.common_ui.util.Destination
18+
import kasem.sm.common_ui.util.Destination.Main
1919

2020
@OptIn(ExperimentalMaterialNavigationApi::class)
2121
@Composable
@@ -31,7 +31,7 @@ fun NavHost(
3131
AnimatedNavHost(
3232
modifier = modifier,
3333
navController = navController,
34-
startDestination = Routes.HomeScreen.route,
34+
startDestination = Destination.HomeScreen.route,
3535
route = Main.route,
3636
) {
3737
attachLoginScreen(navController, snackbarHostState)

app/src/main/java/kasem/sm/slime/ui/navigation/Screens.kt

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import com.google.accompanist.navigation.animation.composable
1919
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
2020
import com.google.accompanist.navigation.material.bottomSheet
2121
import com.slime.ui_home.HomeScreen
22-
import kasem.sm.common_ui.util.Routes
22+
import kasem.sm.common_ui.util.Destination
2323
import kasem.sm.ui_article_list.ListScreen
2424
import kasem.sm.ui_auth.login.LoginScreen
2525
import kasem.sm.ui_auth.register.RegisterScreen
@@ -33,7 +33,7 @@ fun NavGraphBuilder.attachRegistrationScreen(
3333
navController: NavController,
3434
snackbarHostState: SnackbarHostState,
3535
) {
36-
bottomSheet(Routes.RegisterScreen.route) {
36+
bottomSheet(Destination.RegisterScreen.route) {
3737
RegisterScreen(
3838
viewModel = hiltViewModel(),
3939
onRegistrationSuccess = {
@@ -48,7 +48,7 @@ internal fun NavGraphBuilder.attachLoginScreen(
4848
navController: NavController,
4949
snackbarHostState: SnackbarHostState,
5050
) {
51-
bottomSheet(route = Routes.LoginScreen.route) {
51+
bottomSheet(route = Destination.LoginScreen.route) {
5252
LoginScreen(
5353
viewModel = hiltViewModel(),
5454
onLoginSuccess = {
@@ -57,7 +57,7 @@ internal fun NavGraphBuilder.attachLoginScreen(
5757
onSignUpClicked = {
5858
// Remove the login sheet from stack
5959
navController.popBackStack()
60-
navController.navigate(Routes.RegisterScreen.route)
60+
navController.navigate(Destination.RegisterScreen.route)
6161
},
6262
snackbarHostState = snackbarHostState
6363
)
@@ -69,13 +69,13 @@ fun NavGraphBuilder.attachHomeScreen(
6969
navController: NavController,
7070
snackbarHostState: SnackbarHostState,
7171
) {
72-
composable(Routes.HomeScreen.route) {
72+
composable(Destination.HomeScreen.route) {
7373
HomeScreen(
7474
viewModel = hiltViewModel(),
7575
snackbarHostState = snackbarHostState,
7676
imageLoader = imageLoader,
7777
onArticleClick = { id ->
78-
navController.navigate(Routes.articleDetailLink(id))
78+
navController.navigate(Destination.articleDetail(id))
7979
},
8080
navigateTo = { route ->
8181
navController.navigate(route)
@@ -93,7 +93,7 @@ fun NavGraphBuilder.attachExploreScreen(
9393
snackbarHostState: SnackbarHostState,
9494
) {
9595
composable(
96-
route = Routes.ExploreScreen().route,
96+
route = Destination.ExploreScreen().route,
9797
arguments = listOf(
9898
navArgument("slime_topic") {
9999
type = NavType.StringType
@@ -105,10 +105,10 @@ fun NavGraphBuilder.attachExploreScreen(
105105
imageLoader = imageLoader,
106106
snackbarHostState = snackbarHostState,
107107
onArticleClick = { id ->
108-
navController.navigate(Routes.articleDetailLink(id))
108+
navController.navigate(Destination.articleDetail(id))
109109
},
110110
onTopicClick = { title, id ->
111-
navController.navigate(Routes.ListScreen(title, id).route)
111+
navController.navigate(Destination.ListScreen(title, id).route)
112112
}
113113
)
114114
}
@@ -117,7 +117,7 @@ fun NavGraphBuilder.attachExploreScreen(
117117
fun NavGraphBuilder.attachProfileScreen(
118118
navController: NavController
119119
) {
120-
composable(Routes.ProfileScreen.route) {
120+
composable(Destination.ProfileScreen.route) {
121121
ProfileScreen(
122122
viewModel = hiltViewModel(),
123123
onLogOutSuccess = {
@@ -135,10 +135,10 @@ fun NavGraphBuilder.attachArticleDetailScreen(
135135
snackbarHostState: SnackbarHostState,
136136
) {
137137
composable(
138-
route = Routes.ArticleDetailScreen.route,
138+
route = Destination.ArticleDetailScreen.route,
139139
deepLinks = listOf(
140140
navDeepLink {
141-
uriPattern = Routes.articleDetailDeepLink + "{id}"
141+
uriPattern = Destination.articleDetailDeepLink + "{id}"
142142
}
143143
)
144144
) {
@@ -154,7 +154,7 @@ fun NavGraphBuilder.attachSelectTopicsScreen(
154154
navController: NavController,
155155
snackbarHostState: SnackbarHostState,
156156
) {
157-
composable(Routes.SubscribeTopicScreen.route) {
157+
composable(Destination.SubscribeTopicScreen.route) {
158158
SubscribeTopicScreen(
159159
viewModel = hiltViewModel(),
160160
snackbarHostState = snackbarHostState,
@@ -177,7 +177,7 @@ fun NavGraphBuilder.attachListScreen(
177177
navController: NavController
178178
) {
179179
composable(
180-
route = Routes.ListScreen().route,
180+
route = Destination.ListScreen().route,
181181
arguments = listOf(
182182
navArgument("slime_topic") {
183183
type = NavType.StringType
@@ -191,7 +191,7 @@ fun NavGraphBuilder.attachListScreen(
191191
viewModel = hiltViewModel(),
192192
imageLoader = imageLoader,
193193
onArticleClick = { id ->
194-
navController.navigate(Routes.articleDetailLink(id))
194+
navController.navigate(Destination.articleDetail(id))
195195
},
196196
snackbarHostState = snackbarHostState,
197197
navigateTo = {
@@ -205,12 +205,12 @@ fun NavGraphBuilder.attachBookmarksScreen(
205205
imageLoader: ImageLoader,
206206
navController: NavController
207207
) {
208-
composable(Routes.BookmarkScreen.route) {
208+
composable(Destination.BookmarkScreen.route) {
209209
BookmarksScreen(
210210
viewModel = hiltViewModel(),
211211
imageLoader = imageLoader
212212
) { id ->
213-
navController.navigate(Routes.articleDetailLink(id))
213+
navController.navigate(Destination.articleDetail(id))
214214
}
215215
}
216216
}

app/src/main/java/kasem/sm/slime/ui/navigation/SlimeNavigation.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import androidx.compose.material.rememberScaffoldState
1111
import androidx.compose.runtime.Composable
1212
import androidx.compose.ui.Modifier
1313
import androidx.navigation.NavGraph.Companion.findStartDestination
14+
import androidx.navigation.NavHostController
1415
import coil.ImageLoader
15-
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
16+
import com.google.accompanist.navigation.material.BottomSheetNavigator
1617
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
17-
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
1818
import kasem.sm.common_ui.scaffold.SlimeBottomBar
1919
import kasem.sm.common_ui.scaffold.SlimeScaffold
2020
import kasem.sm.common_ui.util.BottomNavigationItems
@@ -23,10 +23,11 @@ import kasem.sm.slime.ui.theme.SlimeTheme
2323
@OptIn(ExperimentalMaterialNavigationApi::class)
2424
@RequiresApi(S)
2525
@Composable
26-
fun SlimeNavigation(imageLoader: ImageLoader) = SlimeTheme {
27-
val bottomSheetNavigator = rememberBottomSheetNavigator()
28-
val navController = rememberAnimatedNavController(bottomSheetNavigator)
29-
26+
fun SlimeNavigation(
27+
imageLoader: ImageLoader,
28+
navController: NavHostController,
29+
bottomSheetNavigator: BottomSheetNavigator,
30+
) = SlimeTheme {
3031
val scaffoldState = rememberScaffoldState()
3132

3233
SlimeScaffold(

build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ buildscript {
4747
classpath Spotless.gradle
4848
classpath Jetbrains.serializationGradle
4949
classpath Extras.dependencyUpdates
50+
classpath Firebase.gmsGoogleService
5051
}
5152
}
5253

@@ -93,12 +94,12 @@ def provideUnitTestDeps(project) {
9394

9495
def provideUiTestDeps(project) {
9596
project.dependencies {
96-
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.2.0-alpha07"
97+
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.2.0-alpha08"
9798
androidTestImplementation "androidx.test.ext:junit:1.1.4-alpha05"
9899
androidTestImplementation "androidx.test.espresso:espresso-core:3.5.0-alpha05"
99100
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.1.1"
100101
androidTestImplementation "com.google.truth:truth:1.1.3"
101-
debugImplementation "androidx.compose.ui:ui-test-manifest:1.2.0-alpha07"
102+
debugImplementation "androidx.compose.ui:ui-test-manifest:1.2.0-alpha08"
102103
}
103104
}
104105

buildSrc/src/main/java/Extras.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
object Extras {
22
private const val dependencyUpdatesVersion = "0.38.0"
33
const val dependencyUpdates = "com.github.ben-manes:gradle-versions-plugin:$dependencyUpdatesVersion"
4-
}
4+
}

0 commit comments

Comments
 (0)