Commit c0462fe1 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Merge branch 'develop-2.x' into notifications

parents a3c3a96a e6fb0c95
......@@ -141,3 +141,5 @@ Temporary Items
.apdisk
# End of https://www.gitignore.io/api/osx
app/libs/
#!/bin/bash
CURRENT_DIR=`pwd`
# The SDK dir should be 2 directories up in the tree, so we use dirname 2 times
# to get the common parent dir of the SDK and the app
tmp=`dirname $CURRENT_DIR`
tmp=`dirname $tmp`
SDK_DIR="$tmp/Rocket.Chat.Kotlin.SDK"
echo "CURRENT DIR: $CURRENT_DIR"
echo "SDK DIR: $SDK_DIR"
# check if there are changes not commited
function git_stat {
local __resultvar=$1
cd $SDK_DIR && git diff --shortstat --exit-code
eval $__resultvar="'$?'"
}
# check for changes already on the index not commited
function git_stat_cached {
local __resultvar=$1
cd $SDK_DIR && git diff --cached --shortstat --exit-code
eval $__resultvar="'$?'"
}
# get the SHA of the lastest commit
function git_sha {
temp_sha=`cd $SDK_DIR && git rev-parse --short HEAD`
echo "$temp_sha"
}
# check if the tree is dirty (has modifications not commited yet)
function check_git_dirty {
git_stat stat
git_stat_cached cached
if [ $stat -eq 0 ] && [ $cached -eq 0 ]; then
echo "not dirty"
return 1
else
echo "is dirty"
return 0
fi
}
# check if the saved last commit is the same as the latest SHA in the tree
function check_last_commit {
if [ ! -f $SDK_DIR/.last_commit_hash ]; then
echo "last_commit_hash not found"
return 0
fi
saved_hash=`cat $SDK_DIR/.last_commit_hash`
last_hash=$(git_sha)
#`cd $SDK_DIR && git rev-parse --short HEAD`
if [ "$saved_hash" == "$last_hash" ]; then
echo "same hash as before $saved_hash = $last_hash"
return 1
fi
echo "different commits, build again"
return 0
}
SHA=$(git_sha)
echo "CURRENT SHA: $SHA"
# if the tree is not dirty, there is no new commit and the .jars are still there, just skip the build
if ! check_git_dirty && ! check_last_commit && [ -f $CURRENT_DIR/libs/common-$SHA.jar ] && [ -f $CURRENT_DIR/libs/core-$SHA.jar ]; then
echo "NO BUILD NEEDED"
exit 0
fi
cd $SDK_DIR && ./gradlew common:assemble && cd $CURRENT_DIR
cd $SDK_DIR && ./gradlew core:assemble && cd $CURRENT_DIR
rm $CURRENT_DIR/libs/common* $CURRENT_DIR/libs/core*
mkdir -p $CURRENT_DIR/libs/
cp -v $SDK_DIR/common/build/libs/common-0.1-SNAPSHOT.jar $CURRENT_DIR/libs/common-$SHA.jar
cp -v $SDK_DIR/core/build/libs/core-0.1-SNAPSHOT.jar $CURRENT_DIR/libs/core-$SHA.jar
echo "$SHA" > $SDK_DIR/.last_commit_hash
exit 0
......@@ -22,6 +22,10 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".dev"
}
}
packagingOptions {
......@@ -32,7 +36,6 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':core')
implementation project(':player')
implementation libraries.kotlin
......@@ -76,7 +79,11 @@ dependencies {
implementation libraries.androidSvg
implementation "com.google.android.gms:play-services-gcm:11.8.0"
implementation libraries.playServicesGcm
implementation libraries.aVLoadingIndicatorView
implementation libraries.textDrawable
testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , {
......@@ -94,4 +101,11 @@ kotlin {
}
}
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
// FIXME - build and install the sdk into the app/libs directory
// We were having some issues with the kapt generated files from the sdk when importing as a module
task compileSdk(type:Exec) {
commandLine './build-sdk.sh'
}
preBuild.dependsOn compileSdk
apply plugin: 'com.google.gms.google-services'
......@@ -13,7 +13,6 @@
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE" />
<application
android:name=".app.RocketChatApplication"
android:allowBackup="true"
......@@ -27,7 +26,8 @@
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme">
android:theme="@style/AuthenticationTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......@@ -37,7 +37,7 @@
</activity>
<activity
android:name=".app.MainActivity"
android:name=".chatrooms.ui.MainActivity"
android:theme="@style/ChatListTheme" />
<activity
......@@ -72,6 +72,9 @@
</intent-filter>
</service>
<activity
android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" />
</application>
</manifest>
\ No newline at end of file
import android.content.Context
import chat.rocket.android.R
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.Period
import org.threeten.bp.*
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle
import org.threeten.bp.format.TextStyle
......@@ -14,20 +12,20 @@ object DateTimeHelper {
private val lastWeek = today.minusWeeks(1)
/**
* Returns a date from a LocalDateTime or the textual representation if the LocalDateTime has a max period of a week from the current date.
* Returns a date from a [LocalDateTime] or the textual representation if the [LocalDateTime] has a max period of a week from the current date.
*
* @param localDateTime The LocalDateTime.
* @param localDateTime The [LocalDateTime].
* @param context The context.
* @return The date or the textual representation from a LocalDateTime.
* @return The date or the textual representation from a [LocalDateTime].
*/
fun getDate(localDateTime: LocalDateTime, context: Context): String {
val localDate = localDateTime.toLocalDate()
return when (localDate) {
today -> localDateTime.toLocalTime().toString()
today -> formatLocalTime(localDateTime.toLocalTime())
yesterday -> context.getString(R.string.msg_yesterday)
else -> {
if (Period.between(lastWeek, localDate).days <= 0) {
formatDate(localDate)
formatLocalDate(localDate)
} else {
localDate.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())
}
......@@ -36,18 +34,33 @@ object DateTimeHelper {
}
/**
* Returns a time from a LocalDateTime.
* Returns a time from a [LocalDateTime].
*
* @param localDateTime The LocalDateTime.
* @return The time from a LocalDateTime.
* @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime].
*/
fun getTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localDateTime.toLocalTime().format(formatter).toString()
}
private fun formatDate(localDate: LocalDate): String {
/**
* Returns a [LocalDateTime] from a [Long].
*
* @param long The [Long]
* @return The [LocalDateTime] from a [Long].
*/
fun getLocalDateTime(long: Long): LocalDateTime {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(long), ZoneId.systemDefault())
}
private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
return localDate.format(formatter).toString()
}
private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString()
}
}
\ No newline at end of file
import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat
import android.widget.EditText
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.TextHelper
import com.amulyakhare.textdrawable.TextDrawable
object DrawableHelper {
private val AVATAR_BACKGROUND_HEXADECIMAL_COLORS = intArrayOf(
0xFFF44336.toInt(), 0xFFE91E63.toInt(), 0xFF9C27B0.toInt(), 0xFF673AB7.toInt(), 0xFF3F51B5.toInt(),
0xFF2196F3.toInt(), 0xFF03A9F4.toInt(), 0xFF00BCD4.toInt(), 0xFF009688.toInt(), 0xFF4CAF50.toInt(),
0xFF8BC34A.toInt(), 0xFFCDDC39.toInt(), 0xFFFFC107.toInt(), 0xFFFF9800.toInt(), 0xFFFF5722.toInt(),
0xFF795548.toInt(), 0xFF9E9E9E.toInt(), 0xFF607D8B.toInt())
/**
* Returns a Drawable from its ID.
*
......@@ -37,7 +44,7 @@ object DrawableHelper {
* @see wrapDrawables
* @see tintDrawable
*/
fun wrapDrawable(drawable: Drawable) = DrawableCompat.wrap(drawable)
fun wrapDrawable(drawable: Drawable): Drawable = DrawableCompat.wrap(drawable)
/**
* Tints an array of Drawable.
......@@ -116,4 +123,29 @@ object DrawableHelper {
}
return userStatusDrawable
}
/**
* Returns a drawable with the first character from a string.
*
* @param string The string to get its first character and to get the avatar background color.
* @return A drawable with the string first character.
*/
fun getTextDrawable(string: String): Drawable {
return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
.endConfig()
.buildRound(TextHelper.getFirstCharacter(string), getAvatarBackgroundColor(string))
}
/**
* Returns a background color to be rendered on the avatar.
*
* @param string Gets the background color based on the provided string.
* @return A hexadecimal color.
* @see (Rocket.Chat/server/startup/avatar.js)
*/
private fun getAvatarBackgroundColor(string: String): Int {
return AVATAR_BACKGROUND_HEXADECIMAL_COLORS[string.length % AVATAR_BACKGROUND_HEXADECIMAL_COLORS.size]
}
}
\ No newline at end of file
package chat.rocket.android.app
import android.app.Activity
import android.graphics.Rect
import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
//TODO: check if this code has memory leak.
class LayoutHelper {
private var childOfContent: View? = null
private var usableHeightPrevious: Int = 0
private var frameLayoutParams: FrameLayout.LayoutParams? = null
/**
* Workaround to adjust the layout when in the full screen mode.
*
* The original author of this code is Joseph Johnson and you can see his answer here: https://stackoverflow.com/a/19494006/4744263
*
* Note that this function has some differences from the original, like using *frameLayoutParams.height = usableHeightNow* instead of
* *frameLayoutParams.height = usableHeightSansKeyboard* (RobertoAllende's comment - from the same link above).
*
* @param activity The Activity to adjust the layout.
*/
fun install(activity: Activity) {
try {
val content = activity.findViewById<View>(android.R.id.content) as FrameLayout
childOfContent = content.getChildAt(0)
childOfContent?.viewTreeObserver?.addOnGlobalLayoutListener(listener)
frameLayoutParams = childOfContent?.layoutParams as FrameLayout.LayoutParams
} catch (exception : ClassCastException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
Log.e("ERROR", exception.message)
}
}
private val listener = ViewTreeObserver.OnGlobalLayoutListener {
resizeChildOfContent()
}
private fun resizeChildOfContent() {
val usableHeightNow = computeUsableHeight()
if (usableHeightNow != usableHeightPrevious) {
val usableHeightSansKeyboard = childOfContent?.rootView?.height ?: 0
val heightDifference = usableHeightSansKeyboard - usableHeightNow
if (heightDifference > usableHeightSansKeyboard / 4) {
// keyboard probably just became visible
frameLayoutParams?.height = usableHeightSansKeyboard - heightDifference
} else {
// keyboard probably just became hidden
frameLayoutParams?.height = usableHeightNow
}
childOfContent?.requestLayout()
usableHeightPrevious = usableHeightNow
}
}
private fun computeUsableHeight(): Int {
val rect = Rect()
childOfContent?.getWindowVisibleDisplayFrame(rect)
return rect.bottom - rect.top
}
fun remove() {
childOfContent?.viewTreeObserver?.removeOnGlobalLayoutListener(listener)
childOfContent = null
frameLayoutParams = null
}
}
\ No newline at end of file
package chat.rocket.android.app
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.chatlist.ChatListFragment
import chat.rocket.android.util.addFragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment("ChatListFragment", R.id.fragment_container) {
ChatListFragment()
}
}
}
\ No newline at end of file
package chat.rocket.android.app.chatlist
import chat.rocket.android.app.User
import org.threeten.bp.LocalDateTime
data class Chat(val user: User,
val name: String,
val type: String,
// Todo replace to Message type instead of String
val lastMessage: String,
val lastMessageDateTime: LocalDateTime,
val totalUnreadMessages: Int)
\ No newline at end of file
package chat.rocket.android.app.chatlist
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.app.User
import kotlinx.android.synthetic.main.fragment_chat_list.*
import org.threeten.bp.LocalDateTime
class ChatListFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_chat_list, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showChatList(createDumpData())
}
// This is just a sample showing 8 chat rooms (aka user subscription). We need to get it rid in a real word. REMARK: remove this comment and this method.
private fun createDumpData(): List<Chat> {
val filipe = User("1", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val lucio = User("2", "Lucio Maciel", "Lucio Maciel", "busy", "https://open.rocket.chat/avatar/lucio.maciel")
val leonardo = User("3", "Leonardo Aramaki", "leonardo.aramaki", "busy", "https://open.rocket.chat/avatar/leonardo.aramaki")
val sing = User("4", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val hetal = User("5", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val matheus = User("6", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val bestrun = User("7", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val djc = User("8", "3djc", "3djc", "online", "https://open.rocket.chat/avatar/filipe.brito")
val dumpChat1 = Chat(leonardo,
"Leonardo Aramaki",
"d",
"Type something",
LocalDateTime.of(2017, 11, 21,1, 3),
1)
val dumpChat2 = Chat(filipe,
"Filipe Brito",
"d",
"Type something...Type something...Type something",
LocalDateTime.of(2017, 11, 22,1, 3),
150)
val dumpChat3 = Chat(lucio,
"Lucio Maciel",
"d",
"Type something",
LocalDateTime.of(2017, 11, 17,1, 3),
0)
val dumpChat4 = Chat(sing,
"mobile-internal",
"p",
"@aaron.ogle @rafael.kellermann same problem over here. Although all the servers show up on the selection.",
LocalDateTime.of(2017, 11, 15,1, 3),
0)
val dumpChat5 = Chat(hetal,
"general",
"c",
"Has joined the channel.",
LocalDateTime.of(2017, 11, 13,1, 3),
0)
val dumpChat6 = Chat(matheus,
"androidnativeapp",
"c",
"Yes @sttyru, but you'll need to implement from the ground up following the docs at docs.rocket.chat where you can see the REST (HTTP) and Real-Time (WebSockets) calls.",
LocalDateTime.of(2017, 11, 14,1, 3),
0)
val dumpChat7 = Chat(bestrun,
"androidnativeapp-2",
"c",
"Just downloaded .zip and imported into Android Studio then build the project.",
LocalDateTime.of(2017, 11, 4,12, 47),
0)
val dumpChat8 = Chat(djc,
"iosnativeapp",
"c",
"Ok, got confused by the blog announcement that shows a screenshot with github oAuth ! Sorry !",
LocalDateTime.of(2017, 11, 4,12, 43),
0)
// creates a list of chat sorted by lastMessageDateTime attribute.
return listOf(dumpChat1, dumpChat2, dumpChat3, dumpChat4, dumpChat5, dumpChat6, dumpChat7, dumpChat8).sortedByDescending { chat -> chat.lastMessageDateTime }
}
// REMARK: The presenter should call this method. The presenter also need to sort the chat list by latest message (compared by its date).
private fun showChatList(dataSet: List<Chat>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recycler_view.adapter = ChatListAdapter(dataSet.toMutableList(), this)
}
}
}
......@@ -8,7 +8,7 @@ import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.app.User
import kotlinx.android.synthetic.main.fragment_chat_list.*
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import org.threeten.bp.LocalDateTime
class MessageFragment : Fragment() {
......
package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import android.content.Context
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity
......@@ -13,13 +13,7 @@ class AuthenticationModule {
@Provides
@PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity)
@Provides
@PerActivity
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
fun provideJob(): Job {
......
......@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class LoginFragmentModule {
@Provides
fun loginView(frag: LoginFragment): LoginView {
return frag
......@@ -26,4 +27,4 @@ class LoginFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesRepository
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.registerPushToken
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun authenticate(usernameOrEmail: String, password: String) {
when {
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
fun authenticate(username: String, password: String) {
// TODO - validate input
try {
client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it?
registerPushToken()
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatTwoFactorException) {
navigator.toTwoFA(usernameOrEmail, password)
} else {
val message = exception.message
if (message != null) {
view.showMessage(message)
} else {
view.showGenericErrorMessage()
}
}
}
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password)
registerPushToken()
navigator.toChatList()
} catch (ex: RocketChatException) {
when (ex) {
is RocketChatTwoFactorException ->
navigator.toTwoFA(navigator.currentServer!!, username, password)
else ->
view.onLoginError(ex.message)
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
} finally {
view.hideLoading()
}
}
}
fun signup() {
navigator.toSignUp(navigator.currentServer!!)
navigator.toSignUp()
}
private suspend fun registerPushToken() {
......
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView {
fun onLoginError(message: String?)
interface LoginView : LoadingView, MessageView, InternetView {
/**
* Shows the oauth view if the server settings allow the login via social accounts.
*
* REMARK: we must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*
* @param value True to show the oauth view, false otherwise.
*/
fun showOauthView(value: Boolean)
/**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/
fun setupFabListener()
/**
* Shows the login by Facebook view.
*/
fun enableLoginByFacebook()
/**
* Shows the login by Github view.
*/
fun enableLoginByGithub()
/**
* Shows the login by Google view.
*/
fun enableLoginByGoogle()
/**
* Shows the login by Linkedin view.
*/
fun enableLoginByLinkedin()
/**
* Shows the login by Meteor view.
*/
fun enableLoginByMeteor()
/**
* Shows the login by Twitter view.
*/
fun enableLoginByTwitter()
/**
* Shows the login by Gitlab view.
*/
fun enableLoginByGitlab()
/**
* Shows the sign up view if the server settings allow the new users registration.
*
* @param value True to show the sign up view, false otherwise.
*/
fun showSignUpView(value: Boolean)
/**
* Alerts the user about a wrong inputted username or email.
*/
fun alertWrongUsernameOrEmail()
/**
* Alerts the user about a wrong inputted password.
*/
fun alertWrongPassword()
}
\ No newline at end of file
package chat.rocket.android.authentication.login.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject
class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter
@Inject lateinit var appContext: Context // TODO we really need it? Check alternatives...
companion object {
private const val SERVER_URL = "server_url"
fun newInstance(url: String) = LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
/*
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
showSignUpView(false)
showOauthView(false)
showLoginButton(true)
} else {
if (isEditTextEmpty()) {
showSignUpView(true)
showOauthView(true)
showLoginButton(false)
}
}
}
var progress: ProgressDialog? = null
lateinit var serverUrl: String
@Inject
lateinit var presenter: LoginPresenter
private var isGlobalLayoutListenerSetUp = false
*/
companion object {
fun newInstance() = LoginFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_log_in, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_log_in)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
activity?.apply {
text_username_or_email.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_username_or_email, InputMethodManager.SHOW_IMPLICIT)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
// Just an example: if the server allow the login via social accounts (oauth authentication) then show the respective interface.
shouldShowOauthView(true)
// In this case we need to setup the layout to hide and show the oauth interface when the soft keyboard is shown (means that the user touched the text_username_or_email and text_password EditText).
setupGlobalLayoutListener()
button_log_in.setOnClickListener {
presenter.authenticate(text_username_or_email.textContent, text_password.textContent)
}
/*
// TODO: THIS IS A PRESENTER CONCERN - REMOVE THAT ! WE SHOULD GET THE SERVER SETTINGS!
// -------------------------------------------------------------------------------------------------------------------
showOauthView(true)
// Show the first three social account's ImageButton (REMARK: we must show at maximum *three* views)
showLoginUsingFacebookImageButton()
showLoginUsingGithubImageButton()
showLoginUsingGoogleImageButton()
enableLoginByFacebook()
enableLoginByGithub()
enableLoginByGoogle()
// Setup the FloatingActionButton to show more social account's ImageButton (it expands the social accounts interface to show more views).
setupFabListener()
// Just an example: if the server allow the new users registration then show the respective interface.
shouldShowSignUpMsgView(true)
button_log_in.setOnClickListener {
presenter.authenticate(text_username_or_email.text.toString(), text_password.text.toString())
}
text_new_to_rocket_chat.setOnClickListener {
presenter.signup()
}
setupSignUpListener()
showSignUpView(true)
// -------------------------------------------------------------------------------------------------------------------
*/
}
/*
override fun onDestroyView() {
scroll_view.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView()
}
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
if (isGlobalLayoutListenerSetUp) {
scroll_view.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
isGlobalLayoutListenerSetUp = false
}
}
*/
override fun showOauthView(value: Boolean) {
// if (value) {
// social_accounts_container.setVisibility(true)
// button_fab.setVisibility(true)
//
// // We need to setup the layout to hide and show the oauth interface when the soft keyboard is shown
// // (means that the user touched the text_username_or_email or text_password EditText to fill that respective fields).
// if (!isGlobalLayoutListenerSetUp) {
// scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
// isGlobalLayoutListenerSetUp = true
// }
// } else {
// social_accounts_container.setVisibility(false)
// button_fab.setVisibility(false)
// }
}
private fun setupGlobalLayoutListener() {
scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
override fun setupFabListener() {
// button_fab.setOnClickListener({
// button_fab.hide()
// showRemainingSocialAccountsView()
// scrollToBottom()
// })
}
val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
shouldShowOauthView(false)
shouldShowSignUpMsgView(false)
shouldShowLoginButton(true)
} else {
if (isEditTextNullOrBlank()) {
shouldShowOauthView(true)
shouldShowSignUpMsgView(true)
shouldShowLoginButton(false)
}
}
override fun enableLoginByFacebook() {
button_facebook.setVisibility(true)
}
private fun shouldShowOauthView(show: Boolean) {
if (show) {
social_accounts_container.visibility = View.VISIBLE
button_fab.visibility = View.VISIBLE
} else {
social_accounts_container.visibility = View.GONE
button_fab.visibility = View.GONE
}
override fun enableLoginByGithub() {
button_github.setVisibility(true)
}
private fun shouldShowSignUpMsgView(show: Boolean) {
if (show) {
text_new_to_rocket_chat.visibility = View.VISIBLE
} else {
text_new_to_rocket_chat.visibility = View.GONE
}
override fun enableLoginByGoogle() {
button_google.setVisibility(true)
}
private fun shouldShowLoginButton(show: Boolean) {
if (show) {
button_log_in.visibility = View.VISIBLE
} else {
button_log_in.visibility = View.GONE
}
override fun enableLoginByLinkedin() {
button_linkedin.setVisibility(true)
}
private fun showLoginUsingFacebookImageButton() {
button_facebook.visibility = View.VISIBLE
override fun enableLoginByMeteor() {
button_meteor.setVisibility(true)
}
private fun showLoginUsingGithubImageButton() {
button_github.visibility = View.VISIBLE
override fun enableLoginByTwitter() {
button_twitter.setVisibility(true)
}
private fun showLoginUsingGoogleImageButton() {
button_google.visibility = View.VISIBLE
override fun enableLoginByGitlab() {
button_gitlab.setVisibility(true)
}
private fun showLoginUsingLinkedinImageButton() {
button_linkedin.visibility = View.VISIBLE
override fun showSignUpView(value: Boolean) = text_new_to_rocket_chat.setVisibility(value)
override fun alertWrongUsernameOrEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username_or_email)
text_username_or_email.requestFocus()
}
private fun showLoginUsingMeteorImageButton() {
button_meteor.visibility = View.VISIBLE
override fun alertWrongPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
text_password.requestFocus()
}
private fun showLoginUsingTwitterImageButton() {
button_twitter.visibility = View.VISIBLE
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
private fun showLoginUsingGitlabImageButton() {
button_gitlab.visibility = View.VISIBLE
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
private fun setupFabListener() {
button_fab.setOnClickListener({
showLoginUsingLinkedinImageButton()
showLoginUsingMeteorImageButton()
showLoginUsingTwitterImageButton()
showLoginUsingGitlabImageButton()
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
scrollToBottom()
hideFab()
})
}
// Returns true if *all* EditText are null or blank.
private fun isEditTextNullOrBlank(): Boolean {
return text_username_or_email.text.isNullOrBlank() && text_password.text.isNullOrBlank()
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1000)
}
private fun hideFab() {
button_fab.postDelayed({
button_fab.hide()
}, 1500)
}
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
private fun tintEditTextDrawableStart() {
activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
}
}
/*
private fun showLoginButton(value: Boolean) {
button_log_in.setVisibility(value)
}
override fun hideLoading() {
progress?.apply {
cancel()
private fun setupSignUpListener() {
val signUp = getString(R.string.title_sign_up)
val newToRocketChat = String.format(getString(R.string.msg_new_to_rocket_chat), signUp)
text_new_to_rocket_chat.text = newToRocketChat
val signUpListener = object : ClickableSpan() {
override fun onClick(view: View) = presenter.signup()
}
progress = null
TextHelper.addLink(text_new_to_rocket_chat, arrayOf(signUp), arrayOf(signUpListener))
}
*/
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_username_or_email.isEnabled = value
text_password.isEnabled = value
// if (isEditTextEmpty()) {
// showSignUpView(value)
// showOauthView(value)
// }
}
/*
// Returns true if *all* EditTexts are empty.
private fun isEditTextEmpty(): Boolean = text_username_or_email.textContent.isBlank() && text_password.textContent.isEmpty()
private fun showRemainingSocialAccountsView() {
social_accounts_container.postDelayed({
enableLoginByLinkedin()
enableLoginByMeteor()
enableLoginByTwitter()
enableLoginByGitlab()
}, 1000)
}
override fun onLoginError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1250)
}
*/
}
\ No newline at end of file
package chat.rocket.android.authentication.presentation
import android.content.Context
import android.content.Intent
import chat.rocket.android.R
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.util.addFragmentBackStack
import chat.rocket.android.webview.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
var currentServer: String? = null
class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) {
lateinit var server: String
lateinit var usernameOrEmail: String
lateinit var password: String
fun toLogin(server: String) {
currentServer = server
this.server = server
activity.addFragmentBackStack("loginFragment", R.id.fragment_container) {
LoginFragment.newInstance(server)
LoginFragment.newInstance()
}
}
fun toChatList() {
val chatRoom = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
fun toTwoFA(usernameOrEmail: String, password: String) {
this.usernameOrEmail = usernameOrEmail
this.password = password
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance()
}
activity.startActivity(chatRoom)
activity.finish()
}
fun toTwoFA(server: String, username: String, password: String) {
currentServer = server
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance(server, username, password)
fun toSignUp() {
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
SignupFragment.newInstance()
}
}
fun toSignUp(server: String) {
currentServer = server
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
SignupFragment.newInstance(server)
fun toTermsOfService() {
val webPageUrl = server + "/terms-of-service" // TODO Move to UrlHelper
activity.startActivity(context.webViewIntent(webPageUrl))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
fun toPrivacyPolicy() {
val webPageUrl = server + "/privacy-policy" // TODO Move to UrlHelper
activity.startActivity(context.webViewIntent(webPageUrl))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
fun toChatList() {
val chatList = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
}
activity.startActivity(chatList)
activity.finish()
}
}
package chat.rocket.android.authentication.server.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ServerFragmentModule {
@Provides
fun serverView(frag: ServerFragment): ServerView {
return frag
}
}
@Provides
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
\ No newline at end of file
package chat.rocket.android.authentication.server.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.core.RocketChatClient
import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
fun login(server: String) {
// TODO - validate server URL and get server settings and info before going to Login screen
navigator.toLogin(server)
fun connect(server: String) {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
// TODO - validate server URL and get server settings and info before going to Login screen
//client.connect(server)
navigator.toLogin(server)
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.server.presentation
interface ServerView
\ No newline at end of file
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface ServerView : LoadingView, MessageView, InternetView
\ No newline at end of file
......@@ -5,44 +5,70 @@ import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.ViewTreeObserver
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.server.presentation.ServerPresenter
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.util.ifEmpty
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.util.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.*
import javax.inject.Inject
class ServerFragment : Fragment(), ServerView {
@Inject lateinit var presenter: ServerPresenter
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
text_server_url.isCursorVisible = KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)
}
@Inject
lateinit var presenter: ServerPresenter
companion object {
fun newInstance() = ServerFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_authentication_server, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_server)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
relative_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
setupOnClickListener()
}
text_server_url.setSelection(text_server_url.length())
override fun onDestroyView() {
super.onDestroyView()
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
button_connect.setOnClickListener {
val url = text_server_url.text.toString().ifEmpty(text_server_url.hint.toString())
presenter.login(server_protocol_label.text.toString() + url)
}
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
companion object {
fun newInstance() = ServerFragment()
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
private fun enableUserInput(value: Boolean) {
button_connect.isEnabled = value
text_server_url.isEnabled = value
}
private fun setupOnClickListener() {
button_connect.setOnClickListener {
val url = text_server_url.textContent.ifEmpty(text_server_url.hintContent)
presenter.connect(text_server_protocol.textContent + url)
}
}
}
\ No newline at end of file
......@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class SignupFragmentModule {
@Provides
fun signupView(frag: SignupFragment): SignupView {
return frag
......@@ -26,4 +27,4 @@ class SignupFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
}
\ No newline at end of file
......@@ -4,8 +4,9 @@ import chat.rocket.android.authentication.signup.ui.SignupFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class SignupFragmentProvider {
@Module
abstract class SignupFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(SignupFragmentModule::class))
@ContributesAndroidInjector(modules = [SignupFragmentModule::class])
abstract fun provideSignupFragment(): SignupFragment
}
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun signup(email: String, name: String, username: String, password: String) {
// TODO - validate input
launchUI(strategy) {
view.showLoading()
try {
val user = client.signup(email, name, username, password)
Timber.d("Created user: $user")
fun signup(name: String, username: String, password: String, email: String) {
when {
name.isBlank() -> {
view.alertBlankName()
}
username.isBlank() -> {
view.alertBlankUsername()
}
password.isEmpty() -> {
view.alertEmptyPassword()
}
email.isBlank() -> {
view.alertBlankEmail()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
val token = client.login(username, password)
Timber.d("Logged in: $token")
try {
client.signup(email, name, username, password) // TODO This function returns a user so should we save it?
client.login(username, password) // TODO This function returns a user token so should we save it?
navigator.toChatList()
} catch (exception: RocketChatException) {
val errorMessage = exception.message
if (errorMessage != null) {
view.showMessage(errorMessage)
} else {
view.showGenericErrorMessage()
}
}
navigator.toChatList()
} catch (ex: RocketChatException) {
view.onSignupError(ex.message)
} finally {
view.hideLoading()
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
}
fun termsOfService() {
navigator.toTermsOfService()
}
fun privacyPolicy() {
navigator.toPrivacyPolicy()
}
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface SignupView : LoadingView {
fun onSignupError(message: String? = "Unknown error")
interface SignupView : LoadingView, MessageView, InternetView {
/**
* Alerts the user about a blank name.
*/
fun alertBlankName()
/**
* Alerts the user about a blank username.
*/
fun alertBlankUsername()
/**
* Alerts the user about a empty password.
*/
fun alertEmptyPassword()
/**
* Alerts the user about a blank email.
*/
fun alertBlankEmail()
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.*
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.util.content
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject
class SignupFragment : Fragment(), SignupView {
@Inject lateinit var presenter: SignupPresenter
@Inject lateinit var appContext: Context
companion object {
private const val SERVER_URL = "server_url"
fun newInstance(url: String) = SignupFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
}
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false)
} else {
text_new_user_agreement.setVisibility(true)
}
}
@Inject
lateinit var presenter: SignupPresenter
var progress: ProgressDialog? = null
lateinit var serverUrl: String
companion object {
fun newInstance() = SignupFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false)
......@@ -52,65 +52,105 @@ class SignupFragment : Fragment(), SignupView {
tintEditTextDrawableStart()
}
setupGlobalLayoutListener()
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
button_sign_up.setOnClickListener {
val email = text_email.content
val name = text_name.content
val username = text_username.content
val password = text_password.content
setUpNewUserAgreementListener()
presenter.signup(email, name, username, password)
button_sign_up.setOnClickListener {
presenter.signup(text_name.textContent, text_username.textContent, text_password.textContent, text_email.textContent)
}
}
override fun onDestroyView() {
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView()
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, this)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
}
override fun alertBlankName() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_name)
text_name.requestFocus()
}
private fun setupGlobalLayoutListener() {
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
override fun alertBlankUsername() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username)
text_username.requestFocus()
}
val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.visibility = View.GONE
} else {
text_new_user_agreement.visibility = View.VISIBLE
}
override fun alertEmptyPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
text_password.requestFocus()
}
override fun alertBlankEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_email)
text_email.requestFocus()
}
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Registering user", true, true)
enableUserInput(false)
view_loading.setVisibility(true)
}
override fun hideLoading() {
progress?.apply {
cancel()
}
progress = null
view_loading.setVisibility(false)
enableUserInput(true)
}
override fun onSignupError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
override fun showMessage(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
}
\ No newline at end of file
override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error))
}
override fun showNoInternetConnection() {
Toast.makeText(activity, getString(R.string.msg_no_internet_connection), Toast.LENGTH_SHORT).show()
}
private fun tintEditTextDrawableStart() {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, appContext)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, appContext)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, appContext)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, appContext)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, appContext, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
}
private fun setUpNewUserAgreementListener() {
val termsOfService = getString(R.string.action_terms_of_service)
val privacyPolicy = getString(R.string.action_privacy_policy)
val newUserAgreement = String.format(getString(R.string.msg_new_user_agreement), termsOfService, privacyPolicy)
text_new_user_agreement.text = newUserAgreement
val termsOfServiceListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.termsOfService()
}
}
val privacyPolicyListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.privacyPolicy()
}
}
TextHelper.addLink(text_new_user_agreement, arrayOf(termsOfService, privacyPolicy), arrayOf(termsOfServiceListener, privacyPolicyListener))
}
private fun enableUserInput(value: Boolean) {
button_sign_up.isEnabled = value
text_name.isEnabled = value
text_username.isEnabled = value
text_password.isEnabled = value
text_email.isEnabled = value
}
}
......@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class TwoFAFragmentModule {
@Provides
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
......
......@@ -6,6 +6,6 @@ import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(TwoFAFragmentModule::class))
@ContributesAndroidInjector(modules = [TwoFAFragmentModule::class])
abstract fun provideTwoFAFragment(): TwoFAFragment
}
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesRepository
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.registerPushToken
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun authenticate(twoFactorAuthenticationCode: String) {
if (twoFactorAuthenticationCode.isBlank()) {
view.alertBlankTwoFactorAuthenticationCode()
} else {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
fun authenticate(username: String, password: String, pin: String) {
// TODO - validate input
try {
client.login(navigator.usernameOrEmail, navigator.password, twoFactorAuthenticationCode) // TODO This function returns a user token so should we save it?
registerPushToken()
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
val message = exception.message
if (message != null) {
view.showMessage(message)
} else {
view.showGenericErrorMessage()
}
}
}
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password, pin)
registerPushToken()
navigator.toChatList()
} catch (ex: RocketChatException) {
view.onLoginError(ex.message)
} finally {
view.hideLoading()
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
fun signup() {
navigator.toSignUp(navigator.currentServer!!)
navigator.toSignUp()
}
private suspend fun registerPushToken() {
......
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface TwoFAView : LoginView
\ No newline at end of file
interface TwoFAView : LoadingView, MessageView, InternetView {
/**
* Alerts the user about a blank Two Factor Authentication code.
*/
fun alertBlankTwoFactorAuthenticationCode()
/**
* Alerts the user about an invalid inputted Two Factor Authentication code.
*/
fun alertInvalidTwoFactorAuthenticationCode()
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.util.content
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject
class TwoFAFragment : Fragment(), TwoFAView {
@Inject lateinit var presenter: TwoFAPresenter
@Inject lateinit var appContext: Context // TODO we really need it? Check alternatives...
companion object {
private const val SERVER_URL = "server_url"
private const val USERNAME = "username"
private const val PASSWORD = "password"
fun newInstance(url: String, username: String, password: String) = TwoFAFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
putString(USERNAME, username)
putString(PASSWORD, password)
}
}
fun newInstance() = TwoFAFragment()
}
var progress: ProgressDialog? = null
lateinit var serverUrl: String
lateinit var username: String
lateinit var password: String
@Inject
lateinit var presenter: TwoFAPresenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
username = arguments?.getString(USERNAME) ?: ""
password = arguments?.getString(PASSWORD) ?: ""
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_two_fa, container, false)
......@@ -57,42 +38,58 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
activity?.apply {
text_two_factor_auth.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_two_factor_auth, InputMethodManager.SHOW_IMPLICIT)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupOnClickListener()
}
button_log_in.setOnClickListener {
presenter.authenticate(username, password, text_two_factor_auth.content)
}
override fun alertBlankTwoFactorAuthenticationCode() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_two_factor_auth)
}
override fun alertInvalidTwoFactorAuthenticationCode() = showMessage(getString(R.string.msg_invalid_2fa_code))
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply {
activity?.apply {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this)
DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
}
}
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_two_factor_auth.isEnabled = value
}
override fun hideLoading() {
progress?.apply {
cancel()
private fun setupOnClickListener() {
button_log_in.setOnClickListener {
presenter.authenticate(text_two_factor_auth.textContent)
}
progress = null
}
override fun onLoginError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.LayoutHelper
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.addFragment
import dagger.android.AndroidInjection
......@@ -14,29 +13,19 @@ import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
val layoutHelper = LayoutHelper()
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
setContentView(R.layout.activity_authentication)
layoutHelper.install(this)
AndroidInjection.inject(this)
addFragment("authenticationServerFragment", R.id.fragment_container) {
ServerFragment.newInstance()
}
}
override fun onDestroy() {
layoutHelper.remove()
super.onDestroy()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
......
package chat.rocket.android.chatrooms.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class ChatRoomsFragmentModule {
@Provides
fun chatRoomsView(frag: ChatRoomsFragment): ChatRoomsView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: ChatRoomsFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
fun provideJob(): Job {
return Job()
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.di
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ChatRoomsFragmentProvider {
@ContributesAndroidInjector(modules = [ChatRoomsFragmentModule::class])
abstract fun provideChatRoomsFragment(): ChatRoomsFragment
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.launchUI
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom
import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, private val strategy: CancelStrategy) {
@Inject lateinit var client: RocketChatClient
fun chatRooms() {
launchUI(strategy) {
view.showLoading()
val chatRooms = client.chatRooms().update
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
view.hideLoading()
}
}
private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.filter(ChatRoom::open)
}
private fun sortChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.sortedByDescending {
chatRoom -> chatRoom.lastMessage?.timestamp
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.model.ChatRoom
interface ChatRoomsView : LoadingView, MessageView {
/**
* Shows the chat rooms.
*
* @param dataSet The data set to show.
*/
fun showChatRooms(dataSet: MutableList<ChatRoom>)
}
\ No newline at end of file
package chat.rocket.android.app.chatlist
package chat.rocket.android.chatrooms.ui
import DateTimeHelper
import DrawableHelper
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import chat.rocket.common.model.BaseRoom.RoomType
import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.*
class ChatListAdapter(private var dataSet: MutableList<Chat>, private val context: Context) : RecyclerView.Adapter<ChatListAdapter.ViewHolder>() {
class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, private val context: Context) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_chat, parent, false)
return ViewHolder(view)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat = dataSet[position]
val chatRoom = dataSet[position]
val chatRoomName = chatRoom.name
holder.userAvatar.setImageURI(chat.user.avatarUri)
holder.chatName.text = chat.name
holder.lastMessage.text = chat.lastMessage
holder.lastMessageDateTime.text = DateTimeHelper.getDate(chat.lastMessageDateTime, context)
holder.chatName.textContent = chatRoomName
when (chat.type) {
"p" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getDrawableFromId(R.drawable.ic_lock_outline_black, context))
"c" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_black, context))
"d" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getUserStatusDrawable(chat.user.status, context))
if (chatRoom.type == RoomType.ONE_TO_ONE) {
// TODO Check the best way to get the current server url.
val canonicalUrl = chatRoom.client.restUrl.toString()
holder.userAvatar.setImageURI(UrlHelper.getAvatarUrl(canonicalUrl, chatRoomName))
holder.userAvatar.setVisibility(true)
} else {
holder.roomAvatar.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
holder.roomAvatar.setVisibility(true)
}
val totalUnreadMessage = chat.totalUnreadMessages
val totalUnreadMessage = chatRoom.unread
when {
totalUnreadMessage in 1..99 -> {
holder.unreadMessage.text = totalUnreadMessage.toString()
holder.unreadMessage.visibility = View.VISIBLE
holder.unreadMessage.textContent = totalUnreadMessage.toString()
holder.unreadMessage.setVisibility(true)
}
totalUnreadMessage > 99 -> {
holder.unreadMessage.text = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
holder.unreadMessage.visibility = View.VISIBLE
holder.unreadMessage.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
holder.unreadMessage.setVisibility(true)
}
}
val lastMessage = chatRoom.lastMessage
val lastMessageSender = lastMessage?.sender
if (lastMessage != null && lastMessageSender != null) {
val message = lastMessage.message
val senderUsername = lastMessageSender.username
when (senderUsername) {
chatRoomName -> {
holder.lastMessage.textContent = message
}
// TODO Change to MySelf
// chatRoom.user?.username -> {
// holder.lastMessage.textContent = context.getString(R.string.msg_you) + ": $message"
// }
else -> {
holder.lastMessage.textContent = "@$senderUsername: $message"
}
}
val localDateTime = DateTimeHelper.getLocalDateTime(lastMessage.timestamp)
holder.lastMessageDateTime.textContent = DateTimeHelper.getDate(localDateTime, context)
}
}
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: SimpleDraweeView = itemView.image_user_avatar
val roomAvatar: ImageView = itemView.image_room_avatar
val chatName: TextView = itemView.text_chat_name
val lastMessage: TextView = itemView.text_last_message
val lastMessageDateTime: TextView = itemView.text_last_message_date_time
......
package chat.rocket.android.chatrooms.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject lateinit var presenter: ChatRoomsPresenter
companion object {
fun newInstance() = ChatRoomsFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_chat_rooms, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.chatRooms()
}
override fun showChatRooms(dataSet: MutableList<ChatRoom>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, 144, 32))
recycler_view.adapter = ChatRoomsAdapter(dataSet, this)
}
}
override fun showLoading() = view_loading.show()
override fun hideLoading() = view_loading.hide()
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
}
\ No newline at end of file
package chat.rocket.android.chatrooms.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.util.addFragment
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AndroidInjection.inject(this)
addFragment("ChatRoomsFragment", R.id.fragment_container) {
ChatRoomsFragment.newInstance()
}
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
}
\ No newline at end of file
package chat.rocket.android.core.behaviours
interface InternetView {
fun showNoInternetConnection()
}
\ No newline at end of file
package chat.rocket.android.core.behaviours
interface LoadingView {
fun showLoading()
fun hideLoading()
}
\ No newline at end of file
package chat.rocket.android.core.behaviours
interface MessageView {
fun showMessage(message: String)
fun showGenericErrorMessage()
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import kotlinx.coroutines.experimental.Job
import javax.inject.Inject
class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
init {
owner.lifecycle.addObserver(this)
}
......
......@@ -10,7 +10,7 @@ import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class))
@Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class])
interface AppComponent {
@Component.Builder
......
package chat.rocket.android.dagger.module
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.di.*
import chat.rocket.android.authentication.di.AuthenticationModule
import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -15,15 +16,14 @@ import dagger.android.ContributesAndroidInjector
abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = arrayOf(
AuthenticationModule::class,
LoginFragmentProvider::class,
@ContributesAndroidInjector(modules = [AuthenticationModule::class,
ServerFragmentProvider::class,
LoginFragmentProvider::class,
SignupFragmentProvider::class,
TwoFAFragmentProvider::class
))
])
abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector
@ContributesAndroidInjector(modules = [ChatRoomsFragmentProvider::class])
abstract fun bindMainActivity(): MainActivity
}
}
\ No newline at end of file
......@@ -5,11 +5,14 @@ import android.arch.persistence.room.Room
import android.content.Context
import chat.rocket.android.BuildConfig
import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import dagger.Module
import dagger.Provides
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
......@@ -17,6 +20,20 @@ import javax.inject.Singleton
@Module
class AppModule {
@Provides
@Singleton
fun provideRocketChatClient(okHttpClient: OkHttpClient, repository: AuthTokenRepository, logger: PlatformLogger): RocketChatClient {
return RocketChatClient.create {
httpClient = okHttpClient
tokenRepository = repository
platformLogger = logger
// TODO remove
restUrl = HttpUrl.parse("https://open.rocket.chat")!!
websocketUrl = "https://open.rocket.chat"
}
}
@Provides
@Singleton
fun provideRocketChatDatabase(context: Application): RocketChatDatabase {
......@@ -35,20 +52,6 @@ class AppModule {
return database.serverDao()
}
/*@Provides
@Singleton
@IntoSet
fun provideHttpLoggingInterceptor(): Interceptor {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.HEADERS
}
return interceptor
}*/
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
......@@ -70,9 +73,15 @@ class AppModule {
}.build()
}
@Provides
@Singleton
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides
@Singleton
fun providePlatformLogger(): PlatformLogger {
return TimberLogger()
return TimberLogger
}
}
package chat.rocket.android.helper
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.View
object AnimationHelper {
/**
* Shakes a view.
*/
fun shakeView(viewToShake: View, x: Float = 2F, num: Int = 0) {
if (num == 6) {
viewToShake.translationX = 0.toFloat()
return
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(ObjectAnimator.ofFloat(viewToShake, "translationX", dp(viewToShake.context, x)))
animatorSet.duration = 50
animatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
shakeView(viewToShake, if (num == 5) 0.toFloat() else -x, num + 1)
}
})
animatorSet.start()
}
/**
* Vibrates the smart phone.
*/
fun vibrateSmartPhone(context: Context) {
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(200)
}
}
private fun dp(context: Context, value: Float): Float {
val density = context.resources.displayMetrics.density
val result = Math.ceil(density.times(value.toDouble()))
return result.toFloat()
}
}
\ No newline at end of file
package chat.rocket.android.app
package chat.rocket.android.helper
import android.graphics.Rect
import android.view.View
......
package chat.rocket.android.helper
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.run
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
object NetworkHelper {
/**
* Checks whether there is internet access.
*
* The original author of this code is Levit and you can see his answer here: https://stackoverflow.com/a/27312494/4744263
*
* @return true if there is internet access, false otherwise.
*/
suspend fun hasInternetAccess(): Boolean = run(CommonPool) {
try {
val socket = Socket()
val inetSocketAddress = InetSocketAddress("8.8.8.8", 53)
socket.connect(inetSocketAddress, 1500)
socket.close()
true
} catch (e: IOException) {
false
}
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.widget.TextView
import chat.rocket.android.util.ifEmpty
object TextHelper {
/**
* Adds clickable link(s) to a TextView.
*
* @param textView The TextView to add clickable links.
* @param links The TextView content(s) to act as clickable links.
* @param clickableSpans The ClickableSpan(s) to handle the onClick listener.
*/
fun addLink(textView: TextView, links: Array<String>, clickableSpans: Array<ClickableSpan>) {
val spannableString = SpannableString(textView.text)
for (i in links.indices) {
val clickableSpan = clickableSpans[i]
val link = links[i]
val startIndexOfLink = textView.text.indexOf(link)
spannableString.setSpan(clickableSpan, startIndexOfLink, startIndexOfLink + link.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.movementMethod = LinkMovementMethod.getInstance()
textView.setText(spannableString, TextView.BufferType.SPANNABLE)
}
/**
* Returns the first character from a string.
*
* @param string The string to get its first character.
* @return The first character from a string.
*/
fun getFirstCharacter(string: String): String {
string.ifEmpty("?")
return string.substring(0, 1).toUpperCase()
}
}
\ No newline at end of file
package chat.rocket.android.helper
object UrlHelper {
/**
* Returns the avatar URL.
*
* @param serverUrl The serverUrl.
* @param chatRoomName The chat room name.
* @return The avatar URL.
*/
fun getAvatarUrl(serverUrl: String, chatRoomName: String): String = serverUrl + "avatar/" + chatRoomName
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;
import io.reactivex.Single;
@Dao
public interface ServerDao {
@Insert(onConflict = OnConflictStrategy.FAIL)
void insertServer(ServerEntity serverEntity);
@Update
void updateServer(ServerEntity serverEntity);
@Delete
void deleteServer(ServerEntity serverEntity);
@Query("SELECT * FROM server")
Single<List<ServerEntity>> getServers();
@Query("SELECT * FROM server WHERE id = :serverId")
Single<ServerEntity> getServer(Long serverId);
}
package chat.rocket.android.server.infraestructure
import android.arch.persistence.room.*
import io.reactivex.Single
@Dao
interface ServerDao {
@Query("SELECT * FROM server")
fun getServers(): Single<List<ServerEntity>>
@Insert(onConflict = OnConflictStrategy.FAIL)
fun insertServer(serverEntity: ServerEntity)
@Update
fun updateServer(serverEntity: ServerEntity)
@Delete
fun deleteServer(serverEntity: ServerEntity)
@Query("SELECT * FROM server WHERE id = :serverId")
fun getServer(serverId: Long?): Single<ServerEntity>
}
......@@ -2,13 +2,20 @@ package chat.rocket.android.util
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction().replace(layoutId, fragment, tag).commit()
supportFragmentManager.beginTransaction()
.replace(layoutId, fragment, tag)
.commit()
}
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction().replace(layoutId, fragment, tag).addToBackStack(tag).commit()
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right)
.replace(layoutId, fragment, tag)
.addToBackStack(tag)
.commit()
}
\ No newline at end of file
package chat.rocket.android.util
import android.support.annotation.LayoutRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
fun String.ifEmpty(value: String): String {
......@@ -9,6 +13,26 @@ fun String.ifEmpty(value: String): String {
return this
}
var TextView.content: String
get() = this.text.toString()
set(value) { this.text = value }
\ No newline at end of file
fun View.setVisibility(value: Boolean) {
visibility = if (value) {
View.VISIBLE
} else {
View.GONE
}
}
fun ViewGroup.inflate(@LayoutRes resource: Int): View {
return LayoutInflater.from(context).inflate(resource, this, false)
}
var TextView.textContent: String
get() = text.toString()
set(value) {
text = value
}
var TextView.hintContent: String
get() = hint.toString()
set(value) {
hint = value
}
......@@ -3,7 +3,8 @@ package chat.rocket.android.util
import chat.rocket.common.util.PlatformLogger
import timber.log.Timber
class TimberLogger : PlatformLogger {
object TimberLogger : PlatformLogger {
override fun debug(s: String) {
Timber.d(s)
}
......
package chat.rocket.android.webview
import android.annotation.SuppressLint
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 chat.rocket.android.R
import kotlinx.android.synthetic.main.activity_web_view.*
import kotlinx.android.synthetic.main.app_bar.*
fun Context.webViewIntent(webPageUrl: String): Intent {
return Intent(this, WebViewActivity::class.java).apply {
putExtra(INTENT_WEB_PAGE_URL, webPageUrl)
}
}
private const val INTENT_WEB_PAGE_URL = "web_page_url"
class WebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: 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" }
setupToolbar()
}
override fun onResume() {
super.onResume()
setupWebView()
}
override fun onBackPressed() {
if (web_view.canGoBack()) {
web_view.goBack()
} else {
finishActivity()
}
}
private fun setupToolbar() {
toolbar.title = getString(R.string.title_legal_terms)
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
toolbar.setNavigationOnClickListener {
finishActivity()
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
web_view.settings.javaScriptEnabled = true
web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view_loading.hide()
}
}
web_view.loadUrl(webPageUrl)
}
private fun finishActivity() {
super.onBackPressed()
overridePendingTransition(R.anim.hold, R.anim.slide_down)
}
}
\ No newline at end of file
package chat.rocket.android.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.support.annotation.DrawableRes
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
/**
* Adds a default or custom divider to specific item views from the adapter's data set.
* @see RecyclerView.ItemDecoration
*/
class DividerItemDecoration() : RecyclerView.ItemDecoration() {
private lateinit var divider: Drawable
private var boundStart = 0
private var boundRight = 0
// Default divider will be used.
constructor(context: Context) : this() {
val attrs = intArrayOf(android.R.attr.listDivider)
val styledAttributes = context.obtainStyledAttributes(attrs)
divider = styledAttributes.getDrawable(0)
styledAttributes.recycle()
}
// Default divider with custom boundaries (start and right) will be used.
constructor(context: Context, boundStart: Int, boundRight: Int) : this() {
val attrs = intArrayOf(android.R.attr.listDivider)
val styledAttributes = context.obtainStyledAttributes(attrs)
divider = styledAttributes.getDrawable(0)
styledAttributes.recycle()
this.boundStart = boundStart
this.boundRight = boundRight
}
// Custom divider will be used.
constructor(context: Context, @DrawableRes drawableResId: Int) : this() {
val customDrawable = ContextCompat.getDrawable(context, drawableResId)
if (customDrawable != null) {
divider = customDrawable
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingLeft + boundStart
val right = (parent.width - parent.paddingRight) - boundRight
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
divider.setBounds(left, top, right, bottom)
divider.draw(c)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="800"
android:fromYDelta="0.0%p"
android:toYDelta="0.0%p" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="0.0%"
android:toYDelta="100.0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="100.0%"
android:toYDelta="0.0%" />
</set>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".webview.WebViewActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar" />
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/layout_app_bar" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator" />
</RelativeLayout>
\ No newline at end of file
......@@ -5,8 +5,8 @@
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fillViewport="true">
android:fillViewport="true"
tools:context=".authentication.login.ui.LoginFragment">
<android.support.constraint.ConstraintLayout
android:id="@+id/middle_container"
......@@ -45,14 +45,40 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" />
<TextView
android:id="@+id/text_new_to_rocket_chat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"
android:gravity="center"
android:textColorLink="@color/colorAccent"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/social_accounts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"
android:animateLayoutChanges="true"
android:layout_marginTop="20dp"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:orientation="vertical"
......@@ -61,7 +87,7 @@
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
app:layout_constraintTop_toBottomOf="@+id/text_new_to_rocket_chat"
tools:visibility="visible">
<TextView
......@@ -165,23 +191,6 @@
app:layout_constraintTop_toBottomOf="@+id/social_accounts_container"
tools:visibility="visible" />
<TextView
android:id="@+id/text_new_to_rocket_chat"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/msg_new_to_rocket_chat"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/social_accounts_container"
tools:visibility="visible" />
<Button
android:id="@+id/button_log_in"
style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:focusableInTouchMode="true"
tools:context=".authentication.server.ui.ServerFragment">
<TextView
android:id="@+id/text_headline"
......@@ -10,7 +15,7 @@
android:text="@string/title_sign_in_your_server" />
<TextView
android:id="@+id/server_protocol_label"
android:id="@+id/text_server_protocol"
style="@style/AuthenticationLabel"
android:layout_below="@id/text_headline"
android:layout_marginTop="32dp"
......@@ -23,12 +28,23 @@
android:layout_below="@id/text_headline"
android:layout_marginStart="0dp"
android:layout_marginTop="32dp"
android:layout_toEndOf="@id/server_protocol_label"
android:layout_toEndOf="@id/text_server_protocol"
android:cursorVisible="false"
android:hint="@string/default_server"
android:imeOptions="actionDone"
android:inputType="textUri"
android:paddingEnd="0dp"
android:paddingStart="0dp" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
<Button
android:id="@+id/button_connect"
style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".authentication.signup.ui.SignupFragment">
<TextView
android:id="@+id/text_headline"
......@@ -61,6 +63,19 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="16dp"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_email"
tools:visibility="visible" />
<TextView
android:id="@+id/text_new_user_agreement"
android:layout_width="wrap_content"
......@@ -69,7 +84,7 @@
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:gravity="center"
android:text="@string/msg_new_user_agreement"
android:textColorLink="@color/colorAccent"
app:layout_constraintBottom_toTopOf="@+id/button_sign_up"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".authentication.twofactor.ui.TwoFAFragment">
<TextView
android:id="@+id/text_headline"
......@@ -19,6 +22,16 @@
android:imeOptions="actionDone"
android:inputType="text" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
<Button
android:id="@+id/button_log_in"
style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".chatrooms.ui.ChatRoomsFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
......@@ -24,4 +26,14 @@
app:floatingSearch_searchBarMarginRight="4dp"
app:floatingSearch_searchBarMarginTop="28dp" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
</RelativeLayout>
\ No newline at end of file
......@@ -4,20 +4,34 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="12dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp">
android:layout_marginTop="12dp">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_user_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
<android.support.constraint.ConstraintLayout
android:id="@+id/avatar_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/middle_container"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
app:layout_constraintTop_toTopOf="parent">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_user_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
app:roundAsCircle="true" />
<ImageView
android:id="@+id/image_room_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
tools:ignore="contentDescription" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/middle_container"
......@@ -25,7 +39,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toRightOf="@+id/image_user_avatar"
app:layout_constraintLeft_toRightOf="@+id/avatar_container"
app:layout_constraintRight_toLeftOf="@+id/right_container">
<TextView
......@@ -36,17 +50,16 @@
android:drawablePadding="5dp"
android:ellipsize="end"
android:maxLines="1"
tools:text="Ronald Perkins" />
tools:text="General" />
<TextView
android:id="@+id/text_last_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:maxLines="2"
app:layout_constraintTop_toBottomOf="@+id/text_chat_name"
tools:text="Type something" />
tools:text="You: Type something" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">Faça login no seu servidor</string>
<string name="title_log_in">Entrar</string>
<string name="title_sign_up">Inscreva-se</string>
<string name="title_legal_terms">Termos Legais</string>
<!-- Actions -->
<string name="action_connect">Conectar</string>
<string name="action_send">Enviar</string>
<string name="action_terms_of_service">Termos de Serviço</string>
<string name="action_privacy_policy">Política de Privacidade</string>
<!-- Regular information messages -->
<string name="msg_no_internet_connection">Sem conexão à internet</string>
<string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string>
<string name="msg_username">nome de usuário</string>
<string name="msg_username_or_email">nome de usuário ou email</string>
<string name="msg_password">senha</string>
<string name="msg_name_and_surname">nome e sobrenome</string>
<string name="msg_email">email</string>
<string name="msg_or_continue_using_social_accounts">Ou continue através de contas sociais</string>
<string name="msg_new_to_rocket_chat">Novo no Rocket Chat? <font color='#FF1976D2'>Inscreva-se</font></string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos\n<font color='#FF1976D2'>Termos de Serviço</font> e <font color='#FF1976D2'>Política de Privacidade</font></string>
<string name="msg_new_to_rocket_chat">Novo no Rocket Chat? %1$s</string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_yesterday">ontem</string>
<string name="msg_message">Messagem</string>
<string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string>
......@@ -29,6 +34,6 @@
<string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_chat_icon">Ícone do chat</string>
<string name="msg_you">Você</string>
</resources>
......@@ -14,4 +14,6 @@
<color name="colorDividerMessageComposer">#D8D8D8</color>
<color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color>
</resources>
\ No newline at end of file
......@@ -5,21 +5,27 @@
<string name="title_sign_in_your_server">Sign in your server</string>
<string name="title_log_in">Log in</string>
<string name="title_sign_up">Sign up</string>
<string name="title_legal_terms">Legal Terms</string>
<!-- Actions -->
<string name="action_connect">Connect</string>
<string name="action_send">Send</string>
<string name="action_terms_of_service">Terms of Service</string>
<string name="action_privacy_policy">Privacy Policy</string>
<!-- Regular information messages -->
<string name="msg_no_internet_connection">No internet connection</string>
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string>
<string name="msg_username">username</string>
<string name="msg_username_or_email">username or email</string>
<string name="msg_password">password</string>
<string name="msg_name_and_surname">name and surname</string>
<string name="msg_email">email</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string>
<string name="msg_new_to_rocket_chat">New to Rocket Chat? <font color='#FF1976D2'>Sign up</font></string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n<font color='#FF1976D2'>Terms of Service</font> and <font color='#FF1976D2'>Privacy Policy</font></string>
<string name="msg_new_to_rocket_chat">New to Rocket Chat? %1$s</string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Yesterday</string>
<string name="msg_message">Message</string>
......@@ -30,6 +36,6 @@
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_chat_icon">Chat icon</string>
<string name="msg_you">You</string>
</resources>
\ No newline at end of file
......@@ -10,7 +10,7 @@
</style>
<style name="AuthenticationTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="ChatListTheme" parent="AppTheme">
......
......@@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "com.android.tools.build:gradle:3.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.1.2'
......@@ -24,6 +24,7 @@ allprojects {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" }
maven { url "http://dl.bintray.com/amulyakhare/maven" } // For TextDrawable.
}
apply from: rootProject.file('dependencies.gradle')
......
......@@ -10,7 +10,7 @@ machine:
GRADLE_OPTS: '-Xmx1024m -Dorg.gradle.jvmargs="-Xmx1024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
JAVA_OPTS: "-Xms518m -Xmx1024m"
pre:
- git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git Rocket.Chat.Kotlin.Sdk
- git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git Rocket.Chat.Kotlin.SDK
dependencies:
pre:
......
......@@ -7,76 +7,86 @@ ext {
targetSdk : 27,
buildTools : '27.0.0',
kotlin : '1.2.10',
coroutine : '0.20',
coroutine : '0.21',
dokka : '0.9.15',
kotshi : '0.3.0-beta2',
// Main dependencies
support : '27.0.2',
constraintLayout : '1.0.2',
dagger : '2.13',
exoPlayer : '2.6.0',
room : '1.0.0',
rxjava : '2.1.4',
rxandroid : '2.0.1',
moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0',
timber : '4.5.1',
threeTenABP : '1.0.5',
fresco : '1.5.0',
frescoImageViewer : '0.5.0',
floatingSearchView: '2.1.1',
androidSvg : '1.2.1',
support : '27.0.2',
constraintLayout : '1.0.2',
dagger : '2.13',
exoPlayer : '2.6.0',
room : '1.0.0',
rxjava : '2.1.4',
rxandroid : '2.0.1',
moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0',
timber : '4.5.1',
threeTenABP : '1.0.5',
fresco : '1.7.1',
frescoImageViewer : '0.5.0',
floatingSearchView : '2.1.1',
androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3',
textDrawable : '1.0.2',
playServices : '11.8.0',
// For testing
junit : '4.12',
truth : '0.36',
expresso : '3.0.1',
mockito : '2.10.0'
junit : '4.12',
truth : '0.36',
expresso : '3.0.1',
mockito : '2.10.0'
]
libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}",
appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}",
rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}",
rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}",
rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}",
playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}",
// For testing
toolsJar : files(Jvm.current().getToolsJar()),
......
include ':app', 'common', 'core', ':player'
project(':common').projectDir = new File(settingsDir, '../Rocket.Chat.Kotlin.SDK/common')
project(':core').projectDir = new File(settingsDir, '../Rocket.Chat.Kotlin.SDK/core')
include ':app', ':player'
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment