Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
AloqaIM-Android
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
AloqaIM-Android
Commits
cd71b275
Commit
cd71b275
authored
Mar 27, 2018
by
Filipe de Lima Brito
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add login with Gitlab (OAuth).
parent
93db630c
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
403 additions
and
175 deletions
+403
-175
gradle-wrapper.properties
app/gradle/wrapper/gradle-wrapper.properties
+1
-1
AndroidManifest.xml
app/src/main/AndroidManifest.xml
+5
-0
LoginPresenter.kt
...droid/authentication/login/presentation/LoginPresenter.kt
+146
-115
LoginView.kt
...et/android/authentication/login/presentation/LoginView.kt
+30
-10
LoginFragment.kt
...t/rocket/android/authentication/login/ui/LoginFragment.kt
+44
-23
AutoCompleteType.kt
.../chat/rocket/android/chatroom/adapter/AutoCompleteType.kt
+3
-3
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+2
-2
PinnedMessagesPresenter.kt
.../android/chatroom/presentation/PinnedMessagesPresenter.kt
+1
-1
ViewModelMapper.kt
...chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
+1
-1
ChatRoomsPresenter.kt
...cket/android/chatrooms/presentation/ChatRoomsPresenter.kt
+1
-1
ChatRoomsFragment.kt
...ava/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
+1
-1
UrlHelper.kt
app/src/main/java/chat/rocket/android/helper/UrlHelper.kt
+21
-0
Text.kt
...src/main/java/chat/rocket/android/util/extensions/Text.kt
+20
-1
CasWebViewActivity.kt
.../chat/rocket/android/webview/cas/ui/CasWebViewActivity.kt
+11
-15
GitlabWebViewActivity.kt
...rocket/android/webview/gitlab/ui/GitlabWebViewActivity.kt
+108
-0
fragment_authentication_log_in.xml
app/src/main/res/layout/fragment_authentication_log_in.xml
+7
-0
gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.properties
+1
-1
No files found.
app/gradle/wrapper/gradle-wrapper.properties
View file @
cd71b275
...
...
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
1
-all.zip
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
6
-all.zip
app/src/main/AndroidManifest.xml
View file @
cd71b275
...
...
@@ -50,6 +50,11 @@
android:windowSoftInputMode=
"adjustResize|stateAlwaysHidden"
android:theme=
"@style/AppTheme"
/>
<activity
android:name=
".webview.gitlab.ui.GitlabWebViewActivity"
android:windowSoftInputMode=
"adjustResize|stateAlwaysHidden"
android:theme=
"@style/AppTheme"
/>
<activity
android:name=
".chatroom.ui.ChatRoomActivity"
android:windowSoftInputMode=
"adjustResize|stateAlwaysHidden"
...
...
app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginPresenter.kt
View file @
cd71b275
...
...
@@ -8,37 +8,78 @@ import chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.server.domain.*
import
chat.rocket.android.server.infraestructure.RocketChatClientFactory
import
chat.rocket.android.util.extensions.generateRandomString
import
chat.rocket.android.util.extensions.isEmailValid
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.android.util.extensions.*
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.
RocketChatTwoFactorExceptio
n
import
chat.rocket.common.
model.Toke
n
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.RocketChatClient
import
chat.rocket.core.internal.rest.*
import
kotlinx.coroutines.experimental.delay
import
timber.log.Timber
import
java.util.concurrent.TimeUnit
import
javax.inject.Inject
private
const
val
TYPE_LOGIN_USER_EMAIL
=
0
private
const
val
TYPE_LOGIN_CAS
=
1
private
const
val
TYPE_LOGIN_OAUTH
=
2
private
const
val
SERVICE_NAME_GITHUB
=
"github"
private
const
val
SERVICE_NAME_GILAB
=
"gitlab"
class
LoginPresenter
@Inject
constructor
(
private
val
view
:
LoginView
,
private
val
strategy
:
CancelStrategy
,
private
val
navigator
:
AuthenticationNavigator
,
private
val
multiServerRepository
:
MultiServerTokenRepository
,
private
val
localRepository
:
LocalRepository
,
private
val
settingsInteractor
:
GetSettingsInteractor
,
private
val
serverInteractor
:
GetCurrentServerInteractor
,
serverInteractor
:
GetCurrentServerInteractor
,
factory
:
RocketChatClientFactory
)
{
// TODO - we should validate the current server when opening the app, and have a nonnull get()
private
val
client
:
RocketChatClient
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
val
server
=
serverInteractor
.
get
()
private
val
settings
=
settingsInteractor
.
get
(
server
!!
)
private
lateinit
var
rocketChatToken
:
Token
private
lateinit
var
usernameOrEmail
:
String
private
lateinit
var
password
:
String
private
lateinit
var
credentialToken
:
String
private
lateinit
var
credentialSecret
:
String
fun
setupView
()
{
val
server
=
serverInteractor
.
get
()
if
(
server
==
null
)
{
navigator
.
toServerScreen
()
return
setupLoginView
()
setupUserRegistrationView
()
setupCasView
()
setupOauthServicesView
()
}
fun
authenticateWithUserAndPassword
(
usernameOrEmail
:
String
,
password
:
String
)
{
when
{
usernameOrEmail
.
isBlank
()
->
{
view
.
alertWrongUsernameOrEmail
()
}
password
.
isEmpty
()
->
{
view
.
alertWrongPassword
()
}
else
->
{
this
.
usernameOrEmail
=
usernameOrEmail
this
.
password
=
password
doAuthentication
(
TYPE_LOGIN_USER_EMAIL
)
}
}
val
settings
=
settingsInteractor
.
get
(
server
)
}
fun
authenticateWithCas
(
token
:
String
)
{
credentialToken
=
token
doAuthentication
(
TYPE_LOGIN_CAS
)
}
fun
authenticateWithOauth
(
token
:
String
,
secret
:
String
)
{
credentialToken
=
token
credentialSecret
=
secret
doAuthentication
(
TYPE_LOGIN_OAUTH
)
}
fun
signup
()
=
navigator
.
toSignUp
()
private
fun
setupLoginView
()
{
if
(
settings
.
isLoginFormEnabled
())
{
view
.
showFormView
()
view
.
setupLoginButtonListener
()
...
...
@@ -46,129 +87,117 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
else
{
view
.
hideFormView
()
}
}
if
(
settings
.
isRegistrationEnabledForNewUsers
())
{
view
.
showSignUpView
()
view
.
setupSignUpView
()
}
private
fun
setupCasView
()
{
if
(
settings
.
isCasAuthenticationEnabled
())
{
val
token
=
generateRandomString
(
17
)
view
.
setupCasButtonListener
(
UrlHelper
.
getCasUrl
(
settings
.
casLoginUrl
(),
server
,
token
),
token
)
view
.
setupCasButtonListener
(
UrlHelper
.
getCasUrl
(
settings
.
casLoginUrl
(),
server
!!
,
token
),
token
)
view
.
showCasButton
()
}
}
var
totalSocialAccountsEnabled
=
0
if
(
settings
.
isFacebookAuthenticationEnabled
())
{
view
.
enableLoginByFacebook
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isGithubAuthenticationEnabled
())
{
view
.
enableLoginByGithub
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isGoogleAuthenticationEnabled
())
{
view
.
enableLoginByGoogle
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isLinkedinAuthenticationEnabled
())
{
view
.
enableLoginByLinkedin
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isMeteorAuthenticationEnabled
())
{
view
.
enableLoginByMeteor
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isTwitterAuthenticationEnabled
())
{
view
.
enableLoginByTwitter
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isGitlabAuthenticationEnabled
())
{
view
.
enableLoginByGitlab
()
totalSocialAccountsEnabled
++
}
if
(
totalSocialAccountsEnabled
>
0
)
{
view
.
showOauthView
()
if
(
totalSocialAccountsEnabled
>
3
)
{
view
.
setupFabListener
()
}
private
fun
setupUserRegistrationView
()
{
if
(
settings
.
isRegistrationEnabledForNewUsers
())
{
view
.
showSignUpView
()
view
.
setupSignUpView
()
}
}
fun
authenticate
(
usernameOrEmail
:
String
,
password
:
String
)
{
val
server
=
serverInteractor
.
get
()
when
{
server
==
null
->
{
navigator
.
toServerScreen
()
}
usernameOrEmail
.
isBlank
()
->
{
view
.
alertWrongUsernameOrEmail
()
}
password
.
isEmpty
()
->
{
view
.
alertWrongPassword
()
}
else
->
{
launchUI
(
strategy
)
{
if
(
NetworkHelper
.
hasInternetAccess
())
{
view
.
disableUserInput
()
view
.
showLoading
()
try
{
val
token
=
if
(
usernameOrEmail
.
isEmailValid
())
{
client
.
loginWithEmail
(
usernameOrEmail
,
password
)
}
else
{
val
settings
=
settingsInteractor
.
get
(
server
)
if
(
settings
.
isLdapAuthenticationEnabled
())
{
client
.
loginWithLdap
(
usernameOrEmail
,
password
)
}
else
{
client
.
login
(
usernameOrEmail
,
password
)
}
}
private
fun
setupOauthServicesView
()
{
launchUI
(
strategy
)
{
try
{
val
services
=
client
.
settingsOauth
().
services
if
(
services
.
isNotEmpty
())
{
var
totalSocialAccountsEnabled
=
0
saveToken
(
server
,
TokenModel
(
token
.
userId
,
token
.
authToken
),
client
.
me
().
username
)
registerPushToken
()
navigator
.
toChatList
()
}
catch
(
exception
:
RocketChatException
)
{
when
(
exception
)
{
is
RocketChatTwoFactorException
->
{
navigator
.
toTwoFA
(
usernameOrEmail
,
password
)
}
else
->
{
exception
.
message
?.
let
{
view
.
showMessage
(
it
)
}.
ifNull
{
view
.
showGenericErrorMessage
()
}
}
}
}
finally
{
view
.
hideLoading
()
view
.
enableUserInput
()
if
(
settings
.
isFacebookAuthenticationEnabled
())
{
view
.
enableLoginByFacebook
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isGithubAuthenticationEnabled
())
{
val
clientId
=
getOauthClientId
(
services
,
SERVICE_NAME_GITHUB
)
if
(
clientId
!=
null
)
{
val
state
=
generateRandomString
(
5
)
view
.
setupGithubButtonListener
(
UrlHelper
.
getGithubOauthUrl
(
clientId
,
state
),
state
)
view
.
enableLoginByGithub
()
totalSocialAccountsEnabled
++
}
}
if
(
settings
.
isGoogleAuthenticationEnabled
())
{
view
.
enableLoginByGoogle
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isLinkedinAuthenticationEnabled
())
{
view
.
enableLoginByLinkedin
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isMeteorAuthenticationEnabled
())
{
view
.
enableLoginByMeteor
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isTwitterAuthenticationEnabled
())
{
view
.
enableLoginByTwitter
()
totalSocialAccountsEnabled
++
}
if
(
settings
.
isGitlabAuthenticationEnabled
())
{
val
clientId
=
getOauthClientId
(
services
,
SERVICE_NAME_GILAB
)
if
(
clientId
!=
null
)
{
// TODO: Improve this code.
val
state
=
"{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}"
.
encodeToBase64
()
view
.
setupGitlabButtonListener
(
UrlHelper
.
getGitlabOauthUrl
(
clientId
,
server
!!
,
state
),
state
)
view
.
enableLoginByGitlab
()
totalSocialAccountsEnabled
++
}
}
if
(
totalSocialAccountsEnabled
>
0
)
{
view
.
showOauthView
()
if
(
totalSocialAccountsEnabled
>
3
)
{
view
.
setupFabListener
()
}
}
else
{
view
.
showNoInternetConnection
()
view
.
hideOauthView
()
}
}
else
{
view
.
hideOauthView
()
}
}
catch
(
exception
:
RocketChatException
)
{
Timber
.
e
(
exception
)
view
.
hideOauthView
()
}
}
}
fun
authenticateWithCas
(
casToken
:
String
)
{
private
fun
doAuthentication
(
loginType
:
Int
)
{
launchUI
(
strategy
)
{
if
(
NetworkHelper
.
hasInternetAccess
())
{
view
.
disableUserInput
()
view
.
showLoading
()
try
{
val
server
=
serverInteractor
.
get
()
if
(
server
!=
null
)
{
delay
(
3
,
TimeUnit
.
SECONDS
)
val
token
=
client
.
loginWithCas
(
casToken
)
saveToken
(
server
,
TokenModel
(
token
.
userId
,
token
.
authToken
),
client
.
me
().
username
)
registerPushToken
()
navigator
.
toChatList
()
}
else
{
navigator
.
toServerScreen
()
when
(
loginType
)
{
TYPE_LOGIN_USER_EMAIL
->
{
rocketChatToken
=
if
(
usernameOrEmail
.
isEmail
())
{
client
.
loginWithEmail
(
usernameOrEmail
,
password
)
}
else
{
if
(
settings
.
isLdapAuthenticationEnabled
())
{
client
.
loginWithLdap
(
usernameOrEmail
,
password
)
}
else
{
client
.
login
(
usernameOrEmail
,
password
)
}
}
}
TYPE_LOGIN_CAS
->
{
delay
(
3
,
TimeUnit
.
SECONDS
)
rocketChatToken
=
client
.
loginWithCas
(
credentialToken
)
}
TYPE_LOGIN_OAUTH
->
{
rocketChatToken
=
client
.
loginWithOauth
(
credentialToken
,
credentialSecret
)
}
}
saveToken
()
registerPushToken
()
navigator
.
toChatList
()
}
catch
(
exception
:
RocketChatException
)
{
exception
.
message
?.
let
{
view
.
showMessage
(
it
)
...
...
@@ -185,12 +214,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
}
fun
signup
()
=
navigator
.
toSignUp
()
private
suspend
fun
saveToken
(
server
:
String
,
tokenModel
:
TokenModel
,
username
:
String
?)
{
multiServerRepository
.
save
(
server
,
tokenModel
)
localRepository
.
save
(
LocalRepository
.
USERNAME_KEY
,
username
)
registerPushToken
()
private
suspend
fun
saveToken
()
{
multiServerRepository
.
save
(
server
!!
,
TokenModel
(
rocketChatToken
.
userId
,
rocketChatToken
.
authToken
))
localRepository
.
save
(
LocalRepository
.
USERNAME_KEY
,
client
.
me
().
username
)
}
private
suspend
fun
registerPushToken
()
{
...
...
@@ -199,4 +225,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
// TODO: Schedule push token registering when it comes up null
}
private
fun
getOauthClientId
(
listMap
:
List
<
Map
<
String
,
String
>>,
serviceName
:
String
):
String
?
{
return
listMap
.
find
{
map
->
map
.
containsValue
(
serviceName
)
}
?.
get
(
"appId"
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginView.kt
View file @
cd71b275
...
...
@@ -37,7 +37,7 @@ interface LoginView : LoadingView, MessageView, InternetView {
/**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings.
*
* REMARK: We must set up the CAS button listener [setupCasButtonListener].
* REMARK: We must set up the CAS button listener
before showing it
[setupCasButtonListener].
*/
fun
showCasButton
()
...
...
@@ -49,8 +49,8 @@ interface LoginView : LoadingView, MessageView, InternetView {
/**
* Setups the CAS button when tapped.
*
* @param casUrl The CAS URL to
login/sign up
with.
* @param casToken The requested
Token
sent to the CAS server.
* @param casUrl The CAS URL to
authenticate
with.
* @param casToken The requested
token to be
sent to the CAS server.
*/
fun
setupCasButtonListener
(
casUrl
:
String
,
casToken
:
String
)
...
...
@@ -96,40 +96,60 @@ interface LoginView : LoadingView, MessageView, InternetView {
fun
hideLoginButton
()
/**
* Shows the "login by Facebook view if it is enable
d
by the server settings.
* Shows the "login by Facebook view if it is enable by the server settings.
*/
fun
enableLoginByFacebook
()
/**
* Shows the "login by Github" view if it is enabled by the server settings.
* Shows the "login by Github" view if it is enable by the server settings.
*
* REMARK: We must set up the Github button listener before enabling it [setupGithubButtonListener].
*/
fun
enableLoginByGithub
()
/**
* Shows the "login by Google" view if it is enabled by the server settings.
* Setups the Github button when tapped.
*
* @param githubUrl The Github OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun
setupGithubButtonListener
(
githubUrl
:
String
,
state
:
String
)
/**
* Shows the "login by Google" view if it is enable by the server settings.
*/
fun
enableLoginByGoogle
()
/**
* Shows the "login by Linkedin" view if it is enable
d
by the server settings.
* Shows the "login by Linkedin" view if it is enable by the server settings.
*/
fun
enableLoginByLinkedin
()
/**
* Shows the "login by Meteor" view if it is enable
d
by the server settings.
* Shows the "login by Meteor" view if it is enable by the server settings.
*/
fun
enableLoginByMeteor
()
/**
* Shows the "login by Twitter" view if it is enable
d
by the server settings.
* Shows the "login by Twitter" view if it is enable by the server settings.
*/
fun
enableLoginByTwitter
()
/**
* Shows the "login by Gitlab" view if it is enabled by the server settings.
* Shows the "login by Gitlab" view if it is enable by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupGitlabButtonListener].
*/
fun
enableLoginByGitlab
()
/**
* Setups the Gitlab button when tapped.
*
* @param gitlabUrl The Gitlab OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun
setupGitlabButtonListener
(
gitlabUrl
:
String
,
state
:
String
)
/**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/
...
...
app/src/main/java/chat/rocket/android/authentication/login/ui/LoginFragment.kt
View file @
cd71b275
...
...
@@ -19,12 +19,17 @@ import chat.rocket.android.authentication.login.presentation.LoginView
import
chat.rocket.android.helper.KeyboardHelper
import
chat.rocket.android.helper.TextHelper
import
chat.rocket.android.util.extensions.*
import
chat.rocket.android.webview.cas.ui.webViewIntent
import
chat.rocket.android.webview.cas.ui.INTENT_CAS_TOKEN
import
chat.rocket.android.webview.cas.ui.casWebViewIntent
import
chat.rocket.android.webview.gitlab.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import
chat.rocket.android.webview.gitlab.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import
chat.rocket.android.webview.gitlab.ui.gitlabWebViewIntent
import
dagger.android.support.AndroidSupportInjection
import
kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import
javax.inject.Inject
internal
const
val
REQUEST_CODE_FOR_CAS
=
1
internal
const
val
REQUEST_CODE_FOR_OAUTH
=
2
class
LoginFragment
:
Fragment
(),
LoginView
{
@Inject
lateinit
var
presenter
:
LoginPresenter
...
...
@@ -64,10 +69,14 @@ class LoginFragment : Fragment(), LoginView {
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
if
(
re
questCode
==
REQUEST_CODE_FOR_CAS
)
{
if
(
re
sultCode
==
Activity
.
RESULT_OK
)
{
if
(
re
sultCode
==
Activity
.
RESULT_OK
)
{
if
(
re
questCode
==
REQUEST_CODE_FOR_CAS
)
{
data
?.
apply
{
presenter
.
authenticateWithCas
(
getStringExtra
(
"cas_token"
))
presenter
.
authenticateWithCas
(
getStringExtra
(
INTENT_CAS_TOKEN
))
}
}
else
if
(
requestCode
==
REQUEST_CODE_FOR_OAUTH
)
{
data
?.
apply
{
presenter
.
authenticateWithOauth
(
getStringExtra
(
INTENT_OAUTH_CREDENTIAL_TOKEN
),
getStringExtra
(
INTENT_OAUTH_CREDENTIAL_SECRET
))
}
}
}
...
...
@@ -121,7 +130,7 @@ class LoginFragment : Fragment(), LoginView {
override
fun
setupLoginButtonListener
()
{
button_log_in
.
setOnClickListener
{
presenter
.
authenticate
(
text_username_or_email
.
textContent
,
text_password
.
textContent
)
presenter
.
authenticate
WithUserAndPassword
(
text_username_or_email
.
textContent
,
text_password
.
textContent
)
}
}
...
...
@@ -147,7 +156,7 @@ class LoginFragment : Fragment(), LoginView {
override
fun
setupCasButtonListener
(
casUrl
:
String
,
casToken
:
String
)
{
button_cas
.
setOnClickListener
{
startActivityForResult
(
context
?.
w
ebViewIntent
(
casUrl
,
casToken
),
REQUEST_CODE_FOR_CAS
)
startActivityForResult
(
context
?.
casW
ebViewIntent
(
casUrl
,
casToken
),
REQUEST_CODE_FOR_CAS
)
activity
?.
overridePendingTransition
(
R
.
anim
.
slide_up
,
R
.
anim
.
hold
)
}
}
...
...
@@ -192,31 +201,46 @@ class LoginFragment : Fragment(), LoginView {
}
override
fun
enableLoginByFacebook
()
{
button_facebook
.
is
Enabled
=
true
button_facebook
.
is
Clickable
=
true
}
override
fun
enableLoginByGithub
()
{
button_github
.
isEnabled
=
true
button_github
.
isClickable
=
true
}
override
fun
setupGithubButtonListener
(
githubUrl
:
String
,
state
:
String
)
{
button_github
.
setOnClickListener
{
// TODO
// startActivityForResult(context?.githubWebViewIntent(url, state), REQUEST_CODE_FOR_OAUTH)
// activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
override
fun
enableLoginByGoogle
()
{
button_google
.
is
Enabled
=
true
button_google
.
is
Clickable
=
true
}
override
fun
enableLoginByLinkedin
()
{
button_linkedin
.
is
Enabled
=
true
button_linkedin
.
is
Clickable
=
true
}
override
fun
enableLoginByMeteor
()
{
button_meteor
.
is
Enabled
=
true
button_meteor
.
is
Clickable
=
true
}
override
fun
enableLoginByTwitter
()
{
button_twitter
.
is
Enabled
=
true
button_twitter
.
is
Clickable
=
true
}
override
fun
enableLoginByGitlab
()
{
button_gitlab
.
isEnabled
=
true
button_gitlab
.
isClickable
=
true
}
override
fun
setupGitlabButtonListener
(
gitlabUrl
:
String
,
state
:
String
)
{
button_gitlab
.
setOnClickListener
{
startActivityForResult
(
context
?.
gitlabWebViewIntent
(
gitlabUrl
,
state
),
REQUEST_CODE_FOR_OAUTH
)
activity
?.
overridePendingTransition
(
R
.
anim
.
slide_up
,
R
.
anim
.
hold
)
}
}
override
fun
setupFabListener
()
{
...
...
@@ -253,8 +277,8 @@ class LoginFragment : Fragment(), LoginView {
social_accounts_container
.
postDelayed
({
(
0
..
social_accounts_container
.
childCount
)
.
mapNotNull
{
social_accounts_container
.
getChildAt
(
it
)
as
?
ImageButton
}
.
filter
{
it
.
is
Enabled
}
.
forEach
{
it
.
visibility
=
View
.
VISIBLE
}
.
filter
{
it
.
is
Clickable
}
.
forEach
{
it
.
setVisible
(
true
)
}
},
1000
)
}
...
...
@@ -284,13 +308,10 @@ class LoginFragment : Fragment(), LoginView {
}
private
fun
showThreeSocialAccountsMethods
()
{
var
count
=
0
for
(
i
in
0
..
social_accounts_container
.
childCount
)
{
val
view
=
social_accounts_container
.
getChildAt
(
i
)
as
?
ImageButton
?:
continue
if
(
view
.
isEnabled
&&
count
<
3
)
{
view
.
visibility
=
View
.
VISIBLE
count
++
}
}
(
0
..
social_accounts_container
.
childCount
)
.
mapNotNull
{
social_accounts_container
.
getChildAt
(
it
)
as
?
ImageButton
}
.
filter
{
it
.
isClickable
}
.
take
(
3
)
.
forEach
{
it
.
setVisible
(
true
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/AutoCompleteType.kt
View file @
cd71b275
...
...
@@ -2,9 +2,9 @@ package chat.rocket.android.chatroom.adapter
import
android.support.annotation.IntDef
const
val
PEOPLE
=
0
L
const
val
ROOMS
=
1
L
const
val
PEOPLE
=
0
const
val
ROOMS
=
1
@Retention
(
AnnotationRetention
.
SOURCE
)
@IntDef
(
value
=
[
PEOPLE
,
ROOMS
]
)
@IntDef
(
PEOPLE
,
ROOMS
)
annotation
class
AutoCompleteType
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
cd71b275
...
...
@@ -53,7 +53,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
val
manager
=
factory
.
create
(
currentServer
)
private
val
client
=
manager
.
client
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
!!
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
private
val
messagesChannel
=
Channel
<
Message
>()
private
var
chatRoomId
:
String
?
=
null
...
...
@@ -402,7 +402,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
fun
spotlight
(
query
:
String
,
@AutoCompleteType
type
:
Long
,
filterSelfOut
:
Boolean
=
false
)
{
fun
spotlight
(
query
:
String
,
@AutoCompleteType
type
:
Int
,
filterSelfOut
:
Boolean
=
false
)
{
launchUI
(
strategy
)
{
try
{
val
(
users
,
rooms
)
=
client
.
spotlight
(
query
)
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/PinnedMessagesPresenter.kt
View file @
cd71b275
...
...
@@ -24,7 +24,7 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag
getSettingsInteractor
:
GetSettingsInteractor
)
{
private
val
client
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
!!
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
private
var
pinnedMessagesListOffset
:
Int
=
0
/**
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
View file @
cd71b275
...
...
@@ -37,7 +37,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
serverInteractor
:
GetCurrentServerInteractor
,
getSettingsInteractor
:
GetSettingsInteractor
)
{
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
!!
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
private
val
baseUrl
=
settings
.
baseUrl
()
private
val
currentUsername
:
String
?
=
localRepository
.
get
(
LocalRepository
.
USERNAME_KEY
)
private
val
token
=
tokenRepository
.
get
()
...
...
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt
View file @
cd71b275
...
...
@@ -36,7 +36,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
val
client
=
manager
.
client
private
var
reloadJob
:
Deferred
<
List
<
ChatRoom
>>?
=
null
private
val
settings
=
settingsRepository
.
get
(
currentServer
)
!!
private
val
settings
=
settingsRepository
.
get
(
currentServer
)
private
val
subscriptionsChannel
=
Channel
<
StreamMessage
<
BaseRoom
>>()
private
val
stateChannel
=
Channel
<
State
>()
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
View file @
cd71b275
...
...
@@ -152,7 +152,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
recycler_view
.
itemAnimator
=
DefaultItemAnimator
()
// TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view
.
adapter
=
ChatRoomsAdapter
(
this
,
settingsRepository
.
get
(
serverInteractor
.
get
()
!!
)
!!
)
{
chatRoom
->
settingsRepository
.
get
(
serverInteractor
.
get
()
!!
))
{
chatRoom
->
presenter
.
loadChatRoom
(
chatRoom
)
}
}
...
...
app/src/main/java/chat/rocket/android/helper/UrlHelper.kt
View file @
cd71b275
...
...
@@ -25,6 +25,27 @@ object UrlHelper {
fun
getCasUrl
(
casLoginUrl
:
String
,
serverUrl
:
String
,
token
:
String
):
String
=
removeTrailingSlash
(
casLoginUrl
)
+
"?service="
+
removeTrailingSlash
(
serverUrl
)
+
"/_cas/"
+
token
/**
* Returns the Github Oauth URL.
*
* @param clientId The GitHub client ID.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Github Oauth URL.
*/
// TODO: Fix github url.
fun
getGithubOauthUrl
(
clientId
:
String
,
state
:
String
):
String
=
"https://github.com/login/oauth/authorize?scope=user:email&client_id=$clientId&state=$state"
/**
* Returns the Gitlab Oauth URL.
*
* @param clientId The Gitlab client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Gitlab Oauth URL.
*/
fun
getGitlabOauthUrl
(
clientId
:
String
,
serverUrl
:
String
,
state
:
String
):
String
=
"https://gitlab.com/oauth/authorize?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/gitlab?close&response_type=code&state=$state&scope=read_user"
/**
* Returns the server's Terms of Service URL.
*
...
...
app/src/main/java/chat/rocket/android/util/extensions/Text.kt
View file @
cd71b275
...
...
@@ -3,12 +3,15 @@ package chat.rocket.android.util.extensions
import
android.text.Spannable
import
android.text.Spanned
import
android.text.TextUtils
import
android.util.Base64
import
android.util.Patterns
import
android.widget.EditText
import
android.widget.TextView
import
chat.rocket.android.widget.emoji.EmojiParser
import
chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import
org.json.JSONObject
import
ru.noties.markwon.Markwon
import
java.net.URLDecoder
import
java.security.SecureRandom
fun
String
.
ifEmpty
(
value
:
String
):
String
{
...
...
@@ -33,7 +36,23 @@ fun EditText.erase() {
}
}
fun
String
.
isEmailValid
():
Boolean
=
Patterns
.
EMAIL_ADDRESS
.
matcher
(
this
).
matches
()
fun
String
.
isEmail
():
Boolean
=
Patterns
.
EMAIL_ADDRESS
.
matcher
(
this
).
matches
()
fun
String
.
encodeToBase64
():
String
{
return
Base64
.
encodeToString
(
this
.
toByteArray
(
charset
(
"UTF-8"
)),
Base64
.
NO_WRAP
)
}
fun
String
.
decodeFromBase64
():
String
{
return
Base64
.
decode
(
this
,
Base64
.
DEFAULT
).
toString
(
charset
(
"UTF-8"
))
}
fun
String
.
decodeUrl
():
String
{
return
URLDecoder
.
decode
(
this
,
"UTF-8"
)
}
fun
String
.
toJsonObject
():
JSONObject
{
return
JSONObject
(
this
)
}
fun
generateRandomString
(
stringLength
:
Int
):
String
{
val
base
=
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
...
...
app/src/main/java/chat/rocket/android/webview/cas/ui/CasWebViewActivity.kt
View file @
cd71b275
...
...
@@ -13,7 +13,7 @@ import chat.rocket.android.R
import
kotlinx.android.synthetic.main.activity_web_view.*
import
kotlinx.android.synthetic.main.app_bar.*
fun
Context
.
w
ebViewIntent
(
webPageUrl
:
String
,
casToken
:
String
):
Intent
{
fun
Context
.
casW
ebViewIntent
(
webPageUrl
:
String
,
casToken
:
String
):
Intent
{
return
Intent
(
this
,
CasWebViewActivity
::
class
.
java
).
apply
{
putExtra
(
INTENT_WEB_PAGE_URL
,
webPageUrl
)
putExtra
(
INTENT_CAS_TOKEN
,
casToken
)
...
...
@@ -21,7 +21,7 @@ fun Context.webViewIntent(webPageUrl: String, casToken: String): Intent {
}
private
const
val
INTENT_WEB_PAGE_URL
=
"web_page_url"
private
const
val
INTENT_CAS_TOKEN
=
"cas_token"
const
val
INTENT_CAS_TOKEN
=
"cas_token"
class
CasWebViewActivity
:
AppCompatActivity
()
{
private
lateinit
var
webPageUrl
:
String
...
...
@@ -49,14 +49,14 @@ class CasWebViewActivity : AppCompatActivity() {
if
(
web_view
.
canGoBack
())
{
web_view
.
goBack
()
}
else
{
finishActivity
(
false
)
closeView
(
)
}
}
private
fun
setupToolbar
()
{
toolbar
.
title
=
getString
(
R
.
string
.
title_authentication
)
toolbar
.
setNavigationIcon
(
R
.
drawable
.
ic_close_white_24dp
)
toolbar
.
setNavigationOnClickListener
{
finishActivity
(
false
)
}
toolbar
.
setNavigationOnClickListener
{
closeView
(
)
}
}
@SuppressLint
(
"SetJavaScriptEnabled"
)
...
...
@@ -64,16 +64,16 @@ class CasWebViewActivity : AppCompatActivity() {
web_view
.
settings
.
javaScriptEnabled
=
true
web_view
.
webViewClient
=
object
:
WebViewClient
()
{
override
fun
onPageStarted
(
view
:
WebView
,
url
:
String
,
favicon
:
Bitmap
?)
{
// The user
can be already
logged in the CAS, so check if the URL contains the "ticket" word
// (that means
he/she is successful authenticated and we don't need to wait until the page is finished
.
// The user
may have already been
logged in the CAS, so check if the URL contains the "ticket" word
// (that means
the user is successful authenticated and we don't need to wait until the page is fully loaded)
.
if
(
url
.
contains
(
"ticket"
))
{
finishActivity
(
true
)
closeView
(
Activity
.
RESULT_OK
)
}
}
override
fun
onPageFinished
(
view
:
WebView
,
url
:
String
)
{
if
(
url
.
contains
(
"ticket"
))
{
finishActivity
(
true
)
closeView
(
Activity
.
RESULT_OK
)
}
else
{
view_loading
.
hide
()
}
...
...
@@ -82,13 +82,9 @@ class CasWebViewActivity : AppCompatActivity() {
web_view
.
loadUrl
(
webPageUrl
)
}
private
fun
finishActivity
(
setResultOk
:
Boolean
)
{
if
(
setResultOk
)
{
setResult
(
Activity
.
RESULT_OK
,
Intent
().
putExtra
(
INTENT_CAS_TOKEN
,
casToken
))
finish
()
}
else
{
super
.
onBackPressed
()
}
private
fun
closeView
(
activityResult
:
Int
=
Activity
.
RESULT_CANCELED
)
{
setResult
(
activityResult
,
Intent
().
putExtra
(
INTENT_CAS_TOKEN
,
casToken
))
finish
()
overridePendingTransition
(
R
.
anim
.
hold
,
R
.
anim
.
slide_down
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/webview/gitlab/ui/GitlabWebViewActivity.kt
0 → 100644
View file @
cd71b275
package
chat.rocket.android.webview.gitlab.ui
import
android.annotation.SuppressLint
import
android.app.Activity
import
android.content.Context
import
android.content.Intent
import
android.os.Bundle
import
android.support.v7.app.AppCompatActivity
import
android.webkit.WebView
import
android.webkit.WebViewClient
import
androidx.net.toUri
import
chat.rocket.android.R
import
chat.rocket.android.util.extensions.decodeUrl
import
chat.rocket.android.util.extensions.toJsonObject
import
kotlinx.android.synthetic.main.activity_web_view.*
import
kotlinx.android.synthetic.main.app_bar.*
import
org.json.JSONObject
fun
Context
.
gitlabWebViewIntent
(
webPageUrl
:
String
,
state
:
String
):
Intent
{
return
Intent
(
this
,
GitlabWebViewActivity
::
class
.
java
).
apply
{
putExtra
(
INTENT_WEB_PAGE_URL
,
webPageUrl
)
putExtra
(
INTENT_STATE
,
state
)
}
}
private
const
val
INTENT_WEB_PAGE_URL
=
"web_page_url"
private
const
val
INTENT_STATE
=
"state"
private
const
val
JSON_CREDENTIAL_TOKEN
=
"credentialToken"
private
const
val
JSON_CREDENTIAL_SECRET
=
"credentialSecret"
const
val
INTENT_OAUTH_CREDENTIAL_TOKEN
=
"credential_token"
const
val
INTENT_OAUTH_CREDENTIAL_SECRET
=
"credential_secret"
// Shows a WebView to the user authenticate with your Gitlab credentials.
class
GitlabWebViewActivity
:
AppCompatActivity
()
{
private
lateinit
var
webPageUrl
:
String
private
lateinit
var
state
:
String
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
activity_web_view
)
webPageUrl
=
intent
.
getStringExtra
(
INTENT_WEB_PAGE_URL
)
requireNotNull
(
webPageUrl
)
{
"no web_page_url provided in Intent extras"
}
state
=
intent
.
getStringExtra
(
INTENT_STATE
)
requireNotNull
(
state
)
{
"no state provided in Intent extras"
}
setupToolbar
()
}
override
fun
onResume
()
{
super
.
onResume
()
setupWebView
()
}
override
fun
onBackPressed
()
{
if
(
web_view
.
canGoBack
())
{
web_view
.
goBack
()
}
else
{
closeView
()
}
}
private
fun
setupToolbar
()
{
toolbar
.
title
=
getString
(
R
.
string
.
title_authentication
)
toolbar
.
setNavigationIcon
(
R
.
drawable
.
ic_close_white_24dp
)
toolbar
.
setNavigationOnClickListener
{
closeView
()
}
}
@SuppressLint
(
"SetJavaScriptEnabled"
)
private
fun
setupWebView
()
{
web_view
.
settings
.
javaScriptEnabled
=
true
web_view
.
webViewClient
=
object
:
WebViewClient
()
{
override
fun
onPageFinished
(
view
:
WebView
,
url
:
String
)
{
if
(
url
.
contains
(
JSON_CREDENTIAL_TOKEN
)
&&
url
.
contains
(
JSON_CREDENTIAL_SECRET
))
{
if
(
isStateValid
(
url
))
{
val
jsonResult
=
url
.
decodeUrl
()
.
substringAfter
(
"#"
)
.
toJsonObject
()
val
credentialToken
=
getCredentialToken
(
jsonResult
)
val
credentialSecret
=
getCredentialSecret
(
jsonResult
)
if
(
credentialToken
.
isNotEmpty
()
&&
credentialSecret
.
isNotEmpty
())
{
closeView
(
Activity
.
RESULT_OK
,
credentialToken
,
credentialSecret
)
}
}
}
view_loading
.
hide
()
}
}
web_view
.
loadUrl
(
webPageUrl
)
}
// If the states matches, then try to get the code, otherwise the request was created by a third party and the process should be aborted.
private
fun
isStateValid
(
url
:
String
):
Boolean
=
url
.
substringBefore
(
"#"
).
toUri
().
getQueryParameter
(
INTENT_STATE
)
==
state
private
fun
getCredentialToken
(
json
:
JSONObject
):
String
=
json
.
optString
(
JSON_CREDENTIAL_TOKEN
)
private
fun
getCredentialSecret
(
json
:
JSONObject
):
String
=
json
.
optString
(
JSON_CREDENTIAL_SECRET
)
private
fun
closeView
(
activityResult
:
Int
=
Activity
.
RESULT_CANCELED
,
credentialToken
:
String
?
=
null
,
credentialSecret
:
String
?
=
null
)
{
setResult
(
activityResult
,
Intent
().
putExtra
(
INTENT_OAUTH_CREDENTIAL_TOKEN
,
credentialToken
).
putExtra
(
INTENT_OAUTH_CREDENTIAL_SECRET
,
credentialSecret
))
finish
()
overridePendingTransition
(
R
.
anim
.
hold
,
R
.
anim
.
slide_down
)
}
}
\ No newline at end of file
app/src/main/res/layout/fragment_authentication_log_in.xml
View file @
cd71b275
...
...
@@ -113,6 +113,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_facebook"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_facebook"
...
...
@@ -124,6 +125,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_github"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_github"
...
...
@@ -135,6 +137,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_google"
android:foreground=
"?android:attr/selectableItemBackground"
android:src=
"@drawable/ic_google"
...
...
@@ -146,6 +149,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_linkedin"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_linkedin"
...
...
@@ -157,6 +161,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_meteor"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_meteor"
...
...
@@ -168,6 +173,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_twitter"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_twitter"
...
...
@@ -179,6 +185,7 @@
android:layout_width=
"290dp"
android:layout_height=
"40dp"
android:layout_marginTop=
"16dp"
android:clickable=
"false"
android:contentDescription=
"@string/msg_content_description_log_in_using_gitlab"
android:foreground=
"?android:attr/selectableItemBackgroundBorderless"
android:src=
"@drawable/ic_gitlab"
...
...
gradle/wrapper/gradle-wrapper.properties
View file @
cd71b275
...
...
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
1
-all.zip
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
6
-all.zip
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment