diff --git a/app/src/foss/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt b/app/src/foss/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt new file mode 100644 index 0000000000..84c64b5263 --- /dev/null +++ b/app/src/foss/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt @@ -0,0 +1,17 @@ +package chat.rocket.android.dynamiclinks + +import android.content.Context +import android.content.Intent +import android.net.Uri +import javax.inject.Inject + +class DynamicLinksForFirebase @Inject constructor(private val context: Context) : DynamicLinks { + + override fun getDynamicLink(intent: Intent, deepLinkCallback: (Uri?) -> Unit? ) { + deepLinkCallback(null) + } + + override fun createDynamicLink(username: String, server: String, deepLinkCallback: (String?) -> Unit?) { + deepLinkCallback(null) + } +} diff --git a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt index c4c9e172d2..8574f665bd 100644 --- a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt +++ b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt @@ -32,7 +32,6 @@ import chat.rocket.core.internal.rest.getCustomEmojis import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.imagepipeline.core.ImagePipelineConfig -import com.google.firebase.FirebaseApp import com.jakewharton.threetenabp.AndroidThreeTen import dagger.android.DispatchingAndroidInjector import dagger.android.HasActivityInjector @@ -100,8 +99,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje AndroidThreeTen.init(this) - FirebaseApp.initializeApp(this) - setupFabric(this) setupFresco() setupTimber() diff --git a/app/src/main/java/chat/rocket/android/authentication/domain/model/DeepLinkInfo.kt b/app/src/main/java/chat/rocket/android/authentication/domain/model/DeepLinkInfo.kt new file mode 100644 index 0000000000..ac0dd47f29 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/authentication/domain/model/DeepLinkInfo.kt @@ -0,0 +1,97 @@ +package chat.rocket.android.authentication.domain.model + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import timber.log.Timber + +// see https://rocket.chat/docs/developer-guides/deeplink/ for documentation + +@SuppressLint("ParcelCreator") +@Parcelize +data class DeepLinkInfo( + val url: String, + val userId: String?, + val token: String?, + val rid: String?, + val roomType: String?, + val roomName: String? +) : Parcelable + +fun Uri.getDeepLinkInfo(): DeepLinkInfo? { + return if (isAuthenticationDeepLink()) { + val host = getQueryParameter("host") + val url = if (host.startsWith("http")) host else "https://$host" + val userId = getQueryParameter("userId") + val token = getQueryParameter("token") + try { + DeepLinkInfo(url, userId, token, null, null, null) + } catch (ex: Exception) { + Timber.d(ex, "Error parsing auth deeplink") + null + } + } else if (isCustomSchemeRoomLink()) { + val hostValue = getQueryParameter("host") + val url = if (hostValue.startsWith("http")) hostValue else "https://$hostValue" + val rid = getQueryParameter("rid") + val pathValue = getQueryParameter("path") + val pathSplit = pathValue.split("/") + val roomType = pathSplit[0] + val roomName = pathSplit[1] + try { + DeepLinkInfo(url, null, null, rid, roomType, roomName) + } catch (ex: Exception) { + Timber.d(ex, "Error parsing custom scheme room link") + null + } + } else if (isWebSchemeRoomLink()) { + val url = "https://$host" + val pathSplit = path.split("/") + val roomType = pathSplit[1] + val roomName = pathSplit[2] + try { + DeepLinkInfo(url, null, null, null, roomType, roomName) + } catch (ex: Exception) { + Timber.d(ex, "Error parsing login deeplink") + null + } + } else null +} + +fun Intent.isSupportedLink(): Boolean { + return (action == Intent.ACTION_VIEW && data != null && + (data.isDynamicLink() || data.isAuthenticationDeepLink() || + data.isCustomSchemeRoomLink() || data.isWebSchemeRoomLink())) +} + +fun Uri.isDynamicLink(): Boolean { + return (host != null && host.contains("page.link", ignoreCase = true)) +} + +// Authentication deep link defined here: https://rocket.chat/docs/developer-guides/deeplink/#authentication +private inline fun Uri.isAuthenticationDeepLink(): Boolean { + if (host == "auth") + return true + else if (host == "go.rocket.chat" && path == "/auth") + return true + return false +} + +// Custom scheme room deep link defined here: https://rocket.chat/docs/developer-guides/deeplink/#channel--group--dm +private inline fun Uri.isCustomSchemeRoomLink(): Boolean { + if (scheme.startsWith("rocketchat") && + host == "room") + return true + return false +} + +// http(s) scheme deep link not yet documented. Ex: https://viasatconnect.com/direct/testuser1 +private inline fun Uri.isWebSchemeRoomLink(): Boolean { + val roomType = path.split("/")[1] + if (scheme.startsWith("http") && + (roomType == "channel" || roomType == "group" || roomType == "direct")) + return true + return false +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/domain/model/LoginDeepLinkInfo.kt b/app/src/main/java/chat/rocket/android/authentication/domain/model/LoginDeepLinkInfo.kt deleted file mode 100644 index 769f3e6757..0000000000 --- a/app/src/main/java/chat/rocket/android/authentication/domain/model/LoginDeepLinkInfo.kt +++ /dev/null @@ -1,40 +0,0 @@ -package chat.rocket.android.authentication.domain.model - -import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri -import android.os.Parcelable -import kotlinx.android.parcel.Parcelize -import timber.log.Timber - -@SuppressLint("ParcelCreator") -@Parcelize -data class LoginDeepLinkInfo( - val url: String, - val userId: String?, - val token: String? -) : Parcelable - -fun Intent.getLoginDeepLinkInfo(): LoginDeepLinkInfo? { - val uri = data - return if (action == Intent.ACTION_VIEW && uri != null && uri.isAuthenticationDeepLink()) { - val host = uri.getQueryParameter("host") - val url = if (host.startsWith("http")) host else "https://$host" - val userId = uri.getQueryParameter("userId") - val token = uri.getQueryParameter("token") - try { - LoginDeepLinkInfo(url, userId, token) - } catch (ex: Exception) { - Timber.d(ex, "Error parsing login deeplink") - null - } - } else null -} - -private inline fun Uri.isAuthenticationDeepLink(): Boolean { - if (host == "auth") - return true - else if (host == "go.rocket.chat" && path == "/auth") - return true - return false -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt index 3ee9795c86..34d7d85530 100644 --- a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt @@ -2,7 +2,7 @@ package chat.rocket.android.authentication.loginoptions.presentation import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.AuthenticationEvent -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.infrastructure.LocalRepository @@ -89,7 +89,7 @@ class LoginOptionsPresenter @Inject constructor( doAuthentication(TYPE_LOGIN_SAML) } - fun authenticateWithDeepLink(deepLinkInfo: LoginDeepLinkInfo) { + fun authenticateWithDeepLink(deepLinkInfo: DeepLinkInfo) { val serverUrl = deepLinkInfo.url setupConnectionInfo(serverUrl) if (deepLinkInfo.userId != null && deepLinkInfo.token != null) { diff --git a/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt b/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt index 55903cdb34..077dbf5be3 100644 --- a/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt @@ -15,7 +15,7 @@ import androidx.fragment.app.Fragment import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsPresenter import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView import chat.rocket.android.authentication.ui.AuthenticationActivity @@ -59,7 +59,6 @@ private const val SAML_SERVICE_BUTTON_COLOR = "saml_service_button_color" private const val TOTAL_SOCIAL_ACCOUNTS = "total_social_accounts" private const val IS_LOGIN_FORM_ENABLED = "is_login_form_enabled" private const val IS_NEW_ACCOUNT_CREATION_ENABLED = "is_new_account_creation_enabled" -private const val DEEP_LINK_INFO = "deep-link-info" internal const val REQUEST_CODE_FOR_OAUTH = 1 internal const val REQUEST_CODE_FOR_CAS = 2 @@ -91,7 +90,7 @@ fun newInstance( totalSocialAccountsEnabled: Int = 0, isLoginFormEnabled: Boolean, isNewAccountCreationEnabled: Boolean, - deepLinkInfo: LoginDeepLinkInfo? = null + deepLinkInfo: DeepLinkInfo? = null ): Fragment { return LoginOptionsFragment().apply { arguments = Bundle(23).apply { @@ -120,7 +119,7 @@ fun newInstance( putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled) putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled) putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled) - putParcelable(DEEP_LINK_INFO, deepLinkInfo) + putParcelable(Constants.DEEP_LINK_INFO, deepLinkInfo) } } } @@ -155,7 +154,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { private var totalSocialAccountsEnabled = 0 private var isLoginFormEnabled = false private var isNewAccountCreationEnabled = false - private var deepLinkInfo: LoginDeepLinkInfo? = null + private var deepLinkInfo: DeepLinkInfo? = null // WIDECHAT - replace the RC login screen with our welcome and login buttons view private var auth_fragment: Int = R.layout.fragment_authentication_widechat_login_options @@ -195,7 +194,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { totalSocialAccountsEnabled = bundle.getInt(TOTAL_SOCIAL_ACCOUNTS) isLoginFormEnabled = bundle.getBoolean(IS_LOGIN_FORM_ENABLED) isNewAccountCreationEnabled = bundle.getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED) - deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO) + deepLinkInfo = bundle.getParcelable(Constants.DEEP_LINK_INFO) } } diff --git a/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationNavigator.kt b/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationNavigator.kt index b303f8876b..474162b4d7 100644 --- a/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationNavigator.kt +++ b/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationNavigator.kt @@ -3,19 +3,30 @@ package chat.rocket.android.authentication.presentation import android.content.Intent import chat.rocket.android.R import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.ui.AuthenticationActivity +import chat.rocket.android.helper.Constants import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.util.extensions.addFragmentBackStack import chat.rocket.android.util.extensions.toPreviousView import chat.rocket.android.webview.ui.webViewIntent +import chat.rocket.common.util.ifNull class AuthenticationNavigator(internal val activity: AuthenticationActivity) { + public var savedDeepLinkInfo: DeepLinkInfo? = null - fun toSignInToYourServer() { + fun saveDeepLinkInfo(deepLinkInfo: DeepLinkInfo) { + savedDeepLinkInfo = deepLinkInfo + } + fun toOnBoarding() { + activity.addFragmentBackStack(ScreenViewEvent.OnBoarding.screenName, R.id.fragment_container) { + chat.rocket.android.authentication.onboarding.ui.newInstance() + } + } + fun toSignInToYourServer(deepLinkInfo: DeepLinkInfo? = null) { activity.addFragmentBackStack(ScreenViewEvent.Server.screenName, R.id.fragment_container) { - chat.rocket.android.authentication.server.ui.newInstance() + chat.rocket.android.authentication.server.ui.newInstance(deepLinkInfo) } } @@ -45,7 +56,7 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { totalSocialAccountsEnabled: Int = 0, isLoginFormEnabled: Boolean = true, isNewAccountCreationEnabled: Boolean = true, - deepLinkInfo: LoginDeepLinkInfo? = null + deepLinkInfo: DeepLinkInfo? = null ) { activity.addFragmentBackStack( ScreenViewEvent.LoginOptions.screenName, @@ -127,13 +138,23 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) } - fun toChatList() { - activity.startActivity(Intent(activity, MainActivity::class.java)) + fun toChatList(serverUrl: String) { + activity.startActivity(activity.changeServerIntent(serverUrl)) activity.finish() } - fun toChatList(serverUrl: String) { - activity.startActivity(activity.changeServerIntent(serverUrl)) + fun toChatList(passedDeepLinkInfo: DeepLinkInfo? = null) { + val deepLinkInfo = if (passedDeepLinkInfo != null) passedDeepLinkInfo else savedDeepLinkInfo + savedDeepLinkInfo = null + + if (deepLinkInfo != null) { + activity.startActivity(Intent(activity, MainActivity::class.java).also { + it.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + it.putExtra(Constants.DEEP_LINK_INFO, deepLinkInfo) + }) + } else { + activity.startActivity(Intent(activity, MainActivity::class.java)) + } activity.finish() } } diff --git a/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationPresenter.kt index af3020e071..cbe91d8c62 100644 --- a/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/presentation/AuthenticationPresenter.kt @@ -1,5 +1,6 @@ package chat.rocket.android.authentication.presentation +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.server.domain.GetAccountInteractor @@ -52,5 +53,9 @@ class AuthenticationPresenter @Inject constructor( fun privacyPolicy(toolbarTitle: String) = serverInteractor.get()?.let { navigator.toWebPage(it.privacyPolicyUrl(), toolbarTitle) } + fun saveDeepLinkInfo(deepLinkInfo: DeepLinkInfo) = navigator.saveDeepLinkInfo(deepLinkInfo) + fun toOnBoarding() = navigator.toOnBoarding() + fun toSignInToYourServer(deepLinkInfo: DeepLinkInfo? = null) = navigator.toSignInToYourServer(deepLinkInfo) fun toChatList() = navigator.toChatList() + fun toChatList(deepLinkInfo: DeepLinkInfo) = navigator.toChatList(deepLinkInfo) } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt index d294506ff4..2770be9398 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt @@ -1,6 +1,6 @@ package chat.rocket.android.authentication.server.presentation -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.lifecycle.CancelStrategy @@ -79,7 +79,7 @@ class ServerPresenter @Inject constructor( } } - fun deepLink(deepLinkInfo: LoginDeepLinkInfo) { + fun deepLink(deepLinkInfo: DeepLinkInfo) { connectToServer(deepLinkInfo.url) { navigator.toLoginOptions(deepLinkInfo.url, deepLinkInfo = deepLinkInfo) } diff --git a/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt b/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt index adeebc8d20..bde6f37317 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt @@ -20,10 +20,11 @@ import chat.rocket.android.BuildConfig import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.server.presentation.ServerPresenter import chat.rocket.android.authentication.server.presentation.ServerView import chat.rocket.android.authentication.ui.AuthenticationActivity +import chat.rocket.android.helper.Constants import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.util.extension.asObservable import chat.rocket.android.util.extensions.hintContent @@ -42,16 +43,20 @@ import kotlinx.android.synthetic.main.fragment_authentication_server.* import okhttp3.HttpUrl import javax.inject.Inject -fun newInstance() = ServerFragment() - -private const val DEEP_LINK_INFO = "DeepLinkInfo" +fun newInstance(deepLinkInfo: DeepLinkInfo?): ServerFragment { + val fragment = ServerFragment() + val args = Bundle() + args.putParcelable(Constants.DEEP_LINK_INFO, deepLinkInfo) + fragment.setArguments(args) + return fragment +} class ServerFragment : Fragment(), ServerView { @Inject lateinit var presenter: ServerPresenter @Inject lateinit var analyticsManager: AnalyticsManager - private var deepLinkInfo: LoginDeepLinkInfo? = null + private var deepLinkInfo: DeepLinkInfo? = null private var protocol = "https://" private var isDomainAppended = false private var appendedText = "" @@ -66,7 +71,7 @@ class ServerFragment : Fragment(), ServerView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO) + deepLinkInfo = arguments?.getParcelable(Constants.DEEP_LINK_INFO) } override fun onCreateView( diff --git a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt index 82f12ad19b..6627f7be1d 100644 --- a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt +++ b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt @@ -6,20 +6,17 @@ import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import chat.rocket.android.R -import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo -import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo +import chat.rocket.android.authentication.domain.model.DeepLinkInfo +import chat.rocket.android.authentication.domain.model.getDeepLinkInfo +import chat.rocket.android.authentication.domain.model.isDynamicLink +import chat.rocket.android.authentication.domain.model.isSupportedLink import chat.rocket.android.authentication.presentation.AuthenticationPresenter +import chat.rocket.android.dynamiclinks.DynamicLinksForFirebase import chat.rocket.android.helper.Constants -import chat.rocket.android.helper.SharedPreferenceHelper -import chat.rocket.android.util.TimberLogger -import chat.rocket.android.util.extensions.addFragment import chat.rocket.common.util.ifNull -import com.google.firebase.dynamiclinks.FirebaseDynamicLinks import dagger.android.AndroidInjection import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector @@ -33,6 +30,8 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var presenter: AuthenticationPresenter + @Inject + lateinit var dynamicLinksManager: DynamicLinksForFirebase val job = Job() override fun onCreate(savedInstanceState: Bundle?) { @@ -40,8 +39,44 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { super.onCreate(savedInstanceState) setContentView(R.layout.activity_authentication) setupToolbar() - loadCredentials() - getDynamicLink(false, intent) + + processIncomingIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + processIncomingIntent(it) + } + } + + private fun processIncomingIntent(intent: Intent) { + if (intent.isSupportedLink()) { + val uri = intent.data + if (uri.isDynamicLink()) { + resolveDynamicLink(intent) + } else { + uri.getDeepLinkInfo()?.let{ + routeDeepLink(it) + } .ifNull { + routeNoLink() + } + } + } else { + routeNoLink() + } + } + + private fun resolveDynamicLink(intent: Intent) { + var deepLinkCallback = { returnedUri: Uri? -> + if (returnedUri == null) { + routeNoLink() + } else { + returnedUri?.getDeepLinkInfo()?.let { + routeDeepLink(it) + } } + } + dynamicLinksManager.getDynamicLink(intent, deepLinkCallback) } private fun setupToolbar() { @@ -75,67 +110,30 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { return super.onOptionsItemSelected(item) } - private fun loadCredentials() { - intent.getLoginDeepLinkInfo()?.let { - showServerFragment(it) - }.ifNull { - val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false) - presenter.loadCredentials(newServer) { isAuthenticated -> + private fun routeDeepLink(deepLinkInfo: DeepLinkInfo) { + if (Constants.WIDECHAT) + presenter.loadCredentials(false) { isAuthenticated -> if (isAuthenticated) { - getDynamicLink(true, intent) + presenter.toChatList(deepLinkInfo) } else { - showOnBoardingFragment() + presenter.saveDeepLinkInfo(deepLinkInfo) + presenter.toOnBoarding() } } - } + else + presenter.toSignInToYourServer(deepLinkInfo) } - private fun showOnBoardingFragment() { - addFragment( - ScreenViewEvent.OnBoarding.screenName, - R.id.fragment_container, - allowStateLoss = true - ) { - chat.rocket.android.authentication.onboarding.ui.newInstance() - } - } - - private fun showServerFragment(deepLinkInfo: LoginDeepLinkInfo) { - addFragment( - ScreenViewEvent.Server.screenName, - R.id.fragment_container, - allowStateLoss = true - ) { - chat.rocket.android.authentication.server.ui.newInstance() + private fun routeNoLink() { + val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false) + presenter.loadCredentials(newServer) { isAuthenticated -> + if (isAuthenticated) { + presenter.toChatList() + } else { + presenter.toOnBoarding() + } } } - - private fun showChatList() { - presenter.toChatList() - } - - private fun getDynamicLink(authenticated: Boolean = false, intent: Intent) { - FirebaseDynamicLinks.getInstance() - .getDynamicLink(intent) - .addOnSuccessListener(this) { pendingDynamicLinkData -> - var deepLink: Uri? = null - if (pendingDynamicLinkData != null) { - deepLink = pendingDynamicLinkData.link - } - - TimberLogger.debug("DeepLink:" + deepLink.toString()) - SharedPreferenceHelper.putString(Constants.DEEP_LINK, deepLink.toString()) - if (authenticated) { - showChatList() - } - } - .addOnFailureListener(this) { e -> TimberLogger.debug("getDynamicLink:onFailure : $e") } - } - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - getDynamicLink(false, intent) - } } const val INTENT_ADD_NEW_SERVER = "INTENT_ADD_NEW_SERVER" diff --git a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt index b26db01616..2e91a80b18 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt @@ -44,14 +44,13 @@ import javax.inject.Inject // WIDECHAT import android.graphics.Color -import android.net.Uri import android.widget.* +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.helper.UserHelper import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.settings.ui.SettingsFragment -import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.extensions.avatarUrl import com.facebook.drawee.view.SimpleDraweeView import kotlinx.android.synthetic.main.app_bar.* @@ -90,15 +89,16 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { private var searchCloseButton: ImageView? = null private var profileButton: SimpleDraweeView? = null private var onlineStatusButton:ImageView?=null - private var deepLink: String? = null + private var deepLinkInfo: DeepLinkInfo? = null // handles that recurring connection status bug in widechat private var currentlyConnected: Boolean? = false companion object { - fun newInstance(chatRoomId: String? = null): ChatRoomsFragment { + fun newInstance(chatRoomId: String? = null, deepLinkInfo: DeepLinkInfo? = null): ChatRoomsFragment { return ChatRoomsFragment().apply { arguments = Bundle(1).apply { putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) + putParcelable(Constants.DEEP_LINK_INFO, deepLinkInfo) } } } @@ -115,6 +115,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { presenter.loadChatRoom(it) chatRoomId = null } + deepLinkInfo = bundle.getParcelable(Constants.DEEP_LINK_INFO) } } @@ -124,7 +125,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { } override fun onResume() { - getDeepLink() // WIDECHAT - cleanup any titles set by other fragments; clear any previous search if (Constants.WIDECHAT) { (activity as AppCompatActivity?)?.supportActionBar?.setDisplayShowTitleEnabled(false) @@ -147,7 +147,10 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { subscribeUi() setupToolbar() setupFab() - getDeepLink() + deepLinkInfo?.let { + processDeepLink(it) + } + deepLinkInfo = null analyticsManager.logScreenView(ScreenViewEvent.ChatRooms) } @@ -517,19 +520,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { } } - private fun getDeepLink() { - - deepLink = SharedPreferenceHelper.getString(Constants.DEEP_LINK, "null") - SharedPreferenceHelper.remove(Constants.DEEP_LINK) - TimberLogger.debug("Retrieved deep link on ChatRooms : $deepLink") - - if(deepLink.isNullOrBlank() || deepLink.equals("null")) { - return - } - - val uri = Uri.parse(deepLink) - val username = uri.lastPathSegment + fun processDeepLink(deepLinkInfo: DeepLinkInfo) { + val username = deepLinkInfo.roomName username.ifNotNullNorEmpty { val localRooms = viewModel.getChatRoomOfUsernameDB(username!!) val filteredLocalRooms = localRooms.filter { itemHolder -> itemHolder.data is RoomUiModel && (itemHolder.data as RoomUiModel).username == username } diff --git a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt index 1a54df6d41..50bc37f749 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt @@ -19,6 +19,7 @@ import chat.rocket.android.dagger.qualifier.ForAuthentication import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManagerFactory +import chat.rocket.android.dynamiclinks.DynamicLinksForFirebase import chat.rocket.android.helper.MessageParser import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository @@ -378,6 +379,12 @@ class AppModule { return AnswersAnalytics() } + @Provides + @Singleton + fun provideDynamicLinkForFirebase(context: Application): DynamicLinksForFirebase { + return DynamicLinksForFirebase(context) + } + @Provides @Singleton fun provideGoogleAnalyticsForFirebase(context: Application): GoogleAnalyticsForFirebase { diff --git a/app/src/main/java/chat/rocket/android/dynamiclinks/DynamicLinks.kt b/app/src/main/java/chat/rocket/android/dynamiclinks/DynamicLinks.kt new file mode 100644 index 0000000000..2576c663c3 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/dynamiclinks/DynamicLinks.kt @@ -0,0 +1,11 @@ +package chat.rocket.android.dynamiclinks + +import android.content.Intent +import android.net.Uri + +interface DynamicLinks { + + fun getDynamicLink(intent: Intent, deepLinkCallback: (Uri?) -> Unit? ) + + fun createDynamicLink(username: String, server: String, deepLinkCallback: (String?) -> Unit?) +} diff --git a/app/src/main/java/chat/rocket/android/helper/Constants.kt b/app/src/main/java/chat/rocket/android/helper/Constants.kt index 0b969dc392..72d0ad6726 100644 --- a/app/src/main/java/chat/rocket/android/helper/Constants.kt +++ b/app/src/main/java/chat/rocket/android/helper/Constants.kt @@ -16,9 +16,8 @@ object Constants { const val WIDECHAT = true const val WIDECHAT_DEV = false - const val DEEP_LINK = "deep_link" - const val AVATAR_SHAPE_CIRCLE = true + const val DEEP_LINK_INFO = "deep_link_info" } object ChatRoomsSortOrder { diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt index c149448922..6b9161654a 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt @@ -1,6 +1,7 @@ package chat.rocket.android.main.presentation import chat.rocket.android.R +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.authentication.ui.newServerIntent import chat.rocket.android.chatroom.ui.chatRoomIntent import chat.rocket.android.chatrooms.ui.ChatRoomsFragment @@ -18,9 +19,9 @@ import chat.rocket.android.webview.adminpanel.ui.AdminPanelWebViewFragment class MainNavigator(internal val activity: MainActivity) { - fun toChatList(chatRoomId: String? = null) { + fun toChatList(chatRoomId: String? = null, deepLinkInfo: DeepLinkInfo? = null) { activity.addFragment(TAG_CHAT_ROOMS_FRAGMENT, R.id.fragment_container) { - ChatRoomsFragment.newInstance(chatRoomId) + ChatRoomsFragment.newInstance(chatRoomId, deepLinkInfo) } } diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt index 0637ec1a95..41e8170ac3 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt @@ -2,19 +2,14 @@ package chat.rocket.android.main.presentation import android.content.Context import android.content.Intent -import android.net.Uri -import android.view.LayoutInflater -import android.widget.EditText -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat.startActivity import chat.rocket.android.R +import chat.rocket.android.authentication.domain.model.DeepLinkInfo import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.model.ChatRoomEntity +import chat.rocket.android.dynamiclinks.DynamicLinksForFirebase import chat.rocket.android.emoji.Emoji import chat.rocket.android.emoji.EmojiRepository import chat.rocket.android.emoji.Fitzpatrick @@ -57,9 +52,6 @@ import chat.rocket.common.model.roomTypeOf import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.Myself import chat.rocket.common.model.RoomType -import com.google.firebase.dynamiclinks.DynamicLink -import com.google.firebase.dynamiclinks.FirebaseDynamicLinks -import com.google.firebase.dynamiclinks.ShortDynamicLink import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.launch @@ -98,6 +90,9 @@ class MainPresenter @Inject constructor( tokenView = view, navigator = navigator ) { + @Inject + lateinit var dynamicLinksManager : DynamicLinksForFirebase + private val currentServer = serverInteractor.get()!! private val manager = managerFactory.create(currentServer) private val dbManager = dbManagerFactory.create(currentServer) @@ -106,7 +101,7 @@ class MainPresenter @Inject constructor( private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!) private val userDataChannel = Channel() - fun toChatList(chatRoomId: String? = null) = navigator.toChatList(chatRoomId) + fun toChatList(chatRoomId: String? = null, deepLinkInfo: DeepLinkInfo? = null) = navigator.toChatList(chatRoomId, deepLinkInfo) fun openDirectMessageChatRoom(username: String) { @@ -332,32 +327,15 @@ class MainPresenter @Inject constructor( val account = getAccountInteractor.get(server)!! val userName = account.userName - FirebaseDynamicLinks.getInstance().createDynamicLink() - .setLink(Uri.parse("$server/direct/$userName")) - .setDomainUriPrefix("https://" + context.getString(R.string.widechat_deeplink_host)) - .setAndroidParameters( - DynamicLink.AndroidParameters.Builder(context.getString(R.string.widechat_package_name)).build()) - .setSocialMetaTagParameters( - DynamicLink.SocialMetaTagParameters.Builder() - .setTitle(userName) - .setDescription("Chat with $userName on " + context.getString(R.string.widechat_server_url)) - .build()) - .buildShortDynamicLink(ShortDynamicLink.Suffix.SHORT) - .addOnSuccessListener { result -> - val link = result.shortLink.toString() - Toast.makeText(context, link, Toast.LENGTH_SHORT).show() - - with(Intent(Intent.ACTION_SEND)) { - type = "text/plain" - putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.msg_check_this_out)) - putExtra(Intent.EXTRA_TEXT, "Default Invitation Text : $link") - context.startActivity(Intent.createChooser(this, context.getString(R.string.msg_share_using))) - } - - }.addOnFailureListener { - // Error - Toast.makeText(context, "Error dynamic link", Toast.LENGTH_SHORT).show() + var deepLinkCallback = { returnedString: String? -> + with(Intent(Intent.ACTION_SEND)) { + type = "text/plain" + putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.msg_check_this_out)) + putExtra(Intent.EXTRA_TEXT, "Default Invitation Text : $returnedString") + context.startActivity(Intent.createChooser(this, context.getString(R.string.msg_share_using))) } + } + dynamicLinksManager.createDynamicLink(userName, server, deepLinkCallback) } } diff --git a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt index b3cdd98ce0..c841785c43 100644 --- a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt +++ b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt @@ -5,6 +5,7 @@ import android.Manifest import android.app.Activity import android.app.AlertDialog import android.app.ProgressDialog +import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import androidx.annotation.IdRes @@ -20,6 +21,9 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import chat.rocket.android.BuildConfig import chat.rocket.android.R +import chat.rocket.android.authentication.domain.model.DeepLinkInfo +import chat.rocket.android.chatrooms.ui.ChatRoomsFragment +import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT import chat.rocket.android.contacts.worker.ContactSyncWorker import chat.rocket.android.main.adapter.AccountsAdapter import chat.rocket.android.main.adapter.Selector @@ -36,6 +40,7 @@ import chat.rocket.android.util.extensions.rotateBy import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.invalidateFirebaseToken import chat.rocket.common.model.UserStatus +import chat.rocket.common.util.ifNull import dagger.android.AndroidInjection import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector @@ -46,17 +51,9 @@ import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.nav_header.view.* import javax.inject.Inject -// TEST -import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT -import chat.rocket.android.chatrooms.ui.ChatRoomsFragment - - // WIDECHAT import chat.rocket.android.helper.Constants -// test -import timber.log.Timber - private const val CURRENT_STATE = "current_state" class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, @@ -73,6 +70,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, private var expanded = false private val headerLayout by lazy { view_navigation.getHeaderView(0) } private var chatRoomId: String? = null + private var deepLinkInfo: DeepLinkInfo? = null private var progressDialog: ProgressDialog? = null private val PERMISSIONS_REQUEST_RW_CONTACTS = 0 @@ -88,6 +86,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, refreshPushToken() syncContacts() chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) + deepLinkInfo = intent.getParcelableExtra(Constants.DEEP_LINK_INFO) presenter.clearNotificationsForChatroom(chatRoomId) presenter.connect() @@ -104,6 +103,21 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, } } + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + var deepLinkInfo = it.getParcelableExtra(Constants.DEEP_LINK_INFO) + if (deepLinkInfo != null) { + val chatRoomsFragment = supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOMS_FRAGMENT) as ChatRoomsFragment + chatRoomsFragment?.let { + it.processDeepLink(deepLinkInfo) + } .ifNull { + isFragmentAdded = false + } + } + } + } + override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState?.putBoolean(CURRENT_STATE, isFragmentAdded) @@ -117,20 +131,13 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, override fun onResume() { supportFragmentManager.popBackStackImmediate("contactsFragment", 1) - Timber.d("########### EAR >> just hit onResume in main activity...") - super.onResume() //syncContacts() if (!isFragmentAdded) { - presenter.toChatList(chatRoomId) + presenter.toChatList(chatRoomId, deepLinkInfo) + deepLinkInfo = null isFragmentAdded = true } - - - var myFrag = supportFragmentManager.findFragmentByTag("ChatRoomsFragment") as ChatRoomsFragment? - myFrag?.getDeepLink() - Timber.d("####### EAR >> this is myFrag to string fwiw...") - Timber.d(myFrag.toString()) } override fun onDestroy() { diff --git a/app/src/play/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt b/app/src/play/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt new file mode 100644 index 0000000000..ca039a3e8d --- /dev/null +++ b/app/src/play/java/chat/rocket/android/dynamiclinks/DynamicLinksForFirebase.kt @@ -0,0 +1,60 @@ +package chat.rocket.android.dynamiclinks + +import android.content.Context +import android.content.Intent +import android.net.Uri +import chat.rocket.android.util.TimberLogger +import chat.rocket.android.R +import com.google.firebase.dynamiclinks.DynamicLink +import com.google.firebase.dynamiclinks.FirebaseDynamicLinks +import com.google.firebase.dynamiclinks.ShortDynamicLink +import javax.inject.Inject + +// DEBUG +import android.widget.Toast + +class DynamicLinksForFirebase @Inject constructor(private var context: Context) : + DynamicLinks { + + private var deepLink: Uri? = null + private var newDeepLink: String? = null + + override fun getDynamicLink(intent: Intent, deepLinkCallback: (Uri?) -> Unit?) { + + FirebaseDynamicLinks.getInstance() + .getDynamicLink(intent) + .addOnSuccessListener { pendingDynamicLinkData -> + if (pendingDynamicLinkData != null) { + deepLink = pendingDynamicLinkData.link + } + deepLinkCallback(deepLink) + } + .addOnFailureListener { e -> TimberLogger.debug("getDynamicLink:onFailure : $e") } + } + + override fun createDynamicLink(username: String, server: String, deepLinkCallback: (String?) -> Unit? ) { + + FirebaseDynamicLinks.getInstance().createDynamicLink() + .setLink(Uri.parse("$server/direct/$username")) + .setDomainUriPrefix("https://" + context.getString(R.string.widechat_deeplink_host)) + .setAndroidParameters( + DynamicLink.AndroidParameters.Builder(context.getString(R.string.widechat_package_name)).build()) + .setSocialMetaTagParameters( + DynamicLink.SocialMetaTagParameters.Builder() + .setTitle(username) + .setDescription("Chat with $username on " + context.getString(R.string.widechat_server_url)) + .build()) + .buildShortDynamicLink(ShortDynamicLink.Suffix.SHORT) + .addOnSuccessListener { result -> + newDeepLink = result.shortLink.toString() + Toast.makeText(context, newDeepLink, Toast.LENGTH_SHORT).show() + deepLinkCallback(newDeepLink) + + }.addOnFailureListener { + // Error + Toast.makeText(context, "Error dynamic link", Toast.LENGTH_SHORT).show() + } + } +} + +