Unverified Commit 782f4851 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge pull request #3 from RocketChat/develop-2.x

Develop-2.x
parents b5d50084 3242e0c5
......@@ -12,32 +12,46 @@ android {
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0"
versionName "2.0.0-beta1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled false
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/core.kotlin_module'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':core')
implementation project(':player')
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesAndroid
implementation libraries.appCompat
implementation libraries.recyclerview
implementation libraries.design
implementation libraries.constraintLayout
implementation libraries.cardView
implementation libraries.dagger
implementation libraries.daggerSupport
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.moshi
implementation libraries.room
kapt libraries.roomProcessor
implementation libraries.roomRxjava
......@@ -48,16 +62,22 @@ dependencies {
implementation libraries.okhttp
implementation libraries.okhttpLogger
implementation libraries.timber
implementation libraries.threeTenABP
implementation libraries.fresco
implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP
implementation libraries.timber
implementation libraries.threeTenABP
implementation libraries.frescoImageViewer
implementation libraries.floatingSearchView
implementation libraries.androidSvg
implementation libraries.aVLoadingIndicatorView
testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , {
exclude group: 'com.android.support', module: 'support-annotations'
......@@ -66,4 +86,10 @@ dependencies {
repositories {
mavenCentral()
}
kotlin {
experimental {
coroutines "enable"
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".app.RocketChatApplication"
......@@ -15,22 +16,11 @@
android:theme="@style/AppTheme">
<activity
android:name=".app.AuthenticationActivity"
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme">
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.MAIN" />-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<!--<category android:name="android.intent.category.LAUNCHER" />-->
<!--</intent-filter>-->
</activity>
<activity
android:name=".app.MainActivity"
android:theme="@style/AppTheme">
android:theme="@style/AuthenticationTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......@@ -38,6 +28,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".app.MainActivity"
android:theme="@style/ChatListTheme" />
<activity
android:name=".app.ChatRoomActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android
import android.app.Activity
import android.app.Fragment
abstract class BaseActivity : Activity() {
protected fun addFragment(fragment: Fragment, tag: String, layoutId: Int) {
fragmentManager.beginTransaction().add(layoutId, fragment, tag).commit()
}
}
\ No newline at end of file
package chat.rocket.android.app
import DrawableHelper
import android.app.Fragment
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.ScrollView
import chat.rocket.android.R
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
class AuthenticationLoginFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_authentication_log_in, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
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()
// Show the first three social account's ImageButton (REMARK: we must show at maximum *three* views)
showLoginUsingFacebookImageButton()
showLoginUsingGithubImageButton()
showLoginUsingGoogleImageButton()
// 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)
}
private fun tintEditTextDrawableStart() {
val context = activity.applicationContext
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, context)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, context)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, context, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
}
private fun setupGlobalLayoutListener() {
scroll_view.viewTreeObserver.addOnGlobalLayoutListener({
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
shouldShowOauthView(false)
shouldShowSignUpMsgView(false)
shouldShowLoginButton(true)
} else {
if (isEditTextNullOrBlank()) {
shouldShowOauthView(true)
shouldShowSignUpMsgView(true)
shouldShowLoginButton(false)
}
}
})
}
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
}
}
private fun shouldShowSignUpMsgView(show: Boolean) {
if (show) {
text_new_to_rocket_chat.visibility = View.VISIBLE
} else {
text_new_to_rocket_chat.visibility = View.GONE
}
}
private fun shouldShowLoginButton(show: Boolean) {
if (show) {
button_log_in.visibility = View.VISIBLE
} else {
button_log_in.visibility = View.GONE
}
}
private fun showLoginUsingFacebookImageButton() {
button_facebook.visibility = View.VISIBLE
}
private fun showLoginUsingGithubImageButton() {
button_github.visibility = View.VISIBLE
}
private fun showLoginUsingGoogleImageButton() {
button_google.visibility = View.VISIBLE
}
private fun showLoginUsingLinkedinImageButton() {
button_linkedin.visibility = View.VISIBLE
}
private fun showLoginUsingMeteorImageButton() {
button_meteor.visibility = View.VISIBLE
}
private fun showLoginUsingTwitterImageButton() {
button_twitter.visibility = View.VISIBLE
}
private fun showLoginUsingGitlabImageButton() {
button_gitlab.visibility = View.VISIBLE
}
private fun setupFabListener() {
button_fab.setOnClickListener({
showLoginUsingLinkedinImageButton()
showLoginUsingMeteorImageButton()
showLoginUsingTwitterImageButton()
showLoginUsingGitlabImageButton()
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()
}
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1000)
}
private fun hideFab() {
button_fab.postDelayed({
button_fab.hide()
}, 1500)
}
}
\ No newline at end of file
package chat.rocket.android.app
import android.app.Fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import chat.rocket.android.R
import kotlinx.android.synthetic.main.fragment_authentication_server.*
class AuthenticationServerFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_authentication_server, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
text_server_url.setSelection(text_server_url.length())
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
}
}
\ No newline at end of file
package chat.rocket.android.app
import DrawableHelper
import android.app.Fragment
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import chat.rocket.android.R
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
class AuthenticationSignUpFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_authentication_sign_up, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupGlobalLayoutListener()
}
private fun tintEditTextDrawableStart() {
val context = activity.applicationContext
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, context)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, context)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, context)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, context)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, context, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
}
private fun setupGlobalLayoutListener() {
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.visibility = View.GONE
} else {
text_new_user_agreement.visibility = View.VISIBLE
}
}
}
}
\ No newline at end of file
package chat.rocket.android.app
import DrawableHelper
import android.app.Fragment
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import chat.rocket.android.R
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
class AuthenticationTwoFAFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_authentication_two_fa, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
}
private fun tintEditTextDrawableStart() {
val context = activity.applicationContext
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, context)
DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, context, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
}
}
\ No newline at end of file
package chat.rocket.android.app
import android.os.Bundle
import chat.rocket.android.BaseActivity
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.chatroom.MessageFragment
import chat.rocket.android.util.addFragment
class AuthenticationActivity : BaseActivity() {
class ChatRoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authentication)
setContentView(R.layout.activity_chat_room)
LayoutHelper.androidBug5497Workaround(this)
addFragment(AuthenticationSignUpFragment(), "authenticationSignUpFragment", R.id.fragment_container)
addFragment("MessageFragment", R.id.fragment_container) {
MessageFragment()
}
}
}
\ No newline at end of file
......@@ -35,6 +35,17 @@ object DateTimeHelper {
}
}
/**
* Returns a 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 {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
return localDate.format(formatter).toString()
......
package chat.rocket.android.app
import android.app.Activity
import android.graphics.Rect
import android.util.Log
import android.view.View
import android.widget.FrameLayout
//TODO: check if this code has memory leak.
object LayoutHelper {
private lateinit var childOfContent: View
private var usableHeightPrevious: Int = 0
private lateinit var frameLayoutParams: FrameLayout.LayoutParams
/**
* 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 androidBug5497Workaround(activity: Activity) {
try {
val content = activity.findViewById<View>(android.R.id.content) as FrameLayout
childOfContent = content.getChildAt(0)
childOfContent.viewTreeObserver.addOnGlobalLayoutListener({ resizeChildOfContent() })
frameLayoutParams = childOfContent.layoutParams as FrameLayout.LayoutParams
} catch (exception : ClassCastException) {
// TODO: are we using the android.util.Log for logging that type of errors?
Log.e("ERROR", exception.message)
}
}
private fun resizeChildOfContent() {
val usableHeightNow = computeUsableHeight()
if (usableHeightNow != usableHeightPrevious) {
val usableHeightSansKeyboard = childOfContent.rootView.height
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
}
}
\ No newline at end of file
package chat.rocket.android.app
import android.os.Bundle
import chat.rocket.android.BaseActivity
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 : BaseActivity() {
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment(ChatListFragment(), "ChatListFragment", R.id.fragment_container)
addFragment("ChatListFragment", R.id.fragment_container) {
ChatListFragment()
}
}
}
\ No newline at end of file
......@@ -3,9 +3,11 @@ package chat.rocket.android.app
import android.app.Activity
import android.app.Application
import chat.rocket.android.BuildConfig
import chat.rocket.android.dagger.DaggerApplicationComponent
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import com.facebook.drawee.backends.pipeline.DraweeConfig
import chat.rocket.android.dagger.DaggerAppComponent
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
......@@ -15,22 +17,32 @@ import javax.inject.Inject
class RocketChatApplication : Application(), HasActivityInjector {
@Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
@Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent.builder()
.application(this)
.build()
.inject(this)
Fresco.initialize(this)
DaggerAppComponent.builder().application(this).build().inject(this)
AndroidThreeTen.init(this)
setupFresco()
setupTimber()
}
private fun setupFresco() {
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.build()
val draweeConfigBuilder = DraweeConfig.newBuilder()
CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder)
Fresco.initialize(this, imagePipelineConfig, draweeConfigBuilder.build())
}
private fun setupTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
......@@ -38,6 +50,6 @@ class RocketChatApplication : Application(), HasActivityInjector {
}
override fun activityInjector(): AndroidInjector<Activity> {
return activityInjector
return activityDispatchingAndroidInjector
}
}
\ No newline at end of file
package chat.rocket.android.app
data class User(val id: String,
val name: String,
val username: String,
val status: String,
val avatarUri: String)
\ 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 userAvatarUri: String,
data class Chat(val user: User,
val name: String,
val type: String,
val userStatus: String?,
// Todo replace to Message type instead of String
val lastMessage: String,
val lastMessageDateTime: LocalDateTime,
val totalUnreadMessages: Int)
\ No newline at end of file
......@@ -22,7 +22,7 @@ class ChatListAdapter(private var dataSet: MutableList<Chat>, private val contex
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat = dataSet[position]
holder.userAvatar.setImageURI(chat.userAvatarUri)
holder.userAvatar.setImageURI(chat.user.avatarUri)
holder.chatName.text = chat.name
holder.lastMessage.text = chat.lastMessage
holder.lastMessageDateTime.text = DateTimeHelper.getDate(chat.lastMessageDateTime, context)
......@@ -30,12 +30,7 @@ class ChatListAdapter(private var dataSet: MutableList<Chat>, private val contex
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" -> {
val userStatus = chat.userStatus
if (userStatus != null) {
DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getUserStatusDrawable(userStatus, context))
}
}
"d" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getUserStatusDrawable(chat.user.status, context))
}
val totalUnreadMessage = chat.totalUnreadMessages
......
package chat.rocket.android.app.chatlist
import android.app.Fragment
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 onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_chat_list, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
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 dumpChat1 = Chat("https://open.rocket.chat/avatar/leonardo.aramaki",
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",
"busy",
"Type something",
LocalDateTime.of(2017, 11, 21,1, 3),
1)
val dumpChat2 = Chat("https://open.rocket.chat/avatar/filipe.brito",
val dumpChat2 = Chat(filipe,
"Filipe Brito",
"d",
"online",
"Type something...Type something...Type something",
LocalDateTime.of(2017, 11, 22,1, 3),
150)
val dumpChat3 = Chat("https://open.rocket.chat/avatar/lucio.maciel",
val dumpChat3 = Chat(lucio,
"Lucio Maciel",
"d",
"away",
"Type something",
LocalDateTime.of(2017, 11, 17,1, 3),
0)
val dumpChat4 = Chat("https://open.rocket.chat/avatar/sing.li",
val dumpChat4 = Chat(sing,
"mobile-internal",
"p",
null,
"@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("https://open.rocket.chat/avatar/hetal",
val dumpChat5 = Chat(hetal,
"general",
"c",
null,
"Has joined the channel.",
LocalDateTime.of(2017, 11, 13,1, 3),
0)
val dumpChat6 = Chat("https://open.rocket.chat/avatar/matheus.cardoso",
val dumpChat6 = Chat(matheus,
"androidnativeapp",
"c",
null,
"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("https://open.rocket.chat/avatar/bestrun",
val dumpChat7 = Chat(bestrun,
"androidnativeapp-2",
"c",
null,
"Just downloaded .zip and imported into Android Studio then build the project.",
LocalDateTime.of(2017, 11, 4,12, 47),
0)
val dumpChat8 = Chat("https://open.rocket.chat/avatar/3djc",
val dumpChat8 = Chat(djc,
"iosnativeapp",
"c",
null,
"Ok, got confused by the blog announcement that shows a screenshot with github oAuth ! Sorry !",
LocalDateTime.of(2017, 11, 4,12, 43),
0)
......@@ -85,9 +87,10 @@ class ChatListFragment : Fragment() {
// 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>) {
val context = activity.applicationContext
recycler_view.adapter = ChatListAdapter(dataSet.toMutableList(), context)
recycler_view.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recycler_view.adapter = ChatListAdapter(dataSet.toMutableList(), this)
}
}
}
\ No newline at end of file
}
package chat.rocket.android.app.chatroom
import chat.rocket.android.app.User
import org.threeten.bp.LocalDateTime
data class Message(val user: User,
val textContent: String?,
val imageAttachmentUri: String?,
val localDatetime: LocalDateTime)
\ No newline at end of file
package chat.rocket.android.app.chatroom
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 chat.rocket.android.R
import chat.rocket.android.app.User
import kotlinx.android.synthetic.main.fragment_chat_list.*
import org.threeten.bp.LocalDateTime
class MessageFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_message, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showMessageList(createDumpData())
}
// This is just a sample showing 2 messages in the chat room. We need to get it rid in a real word. REMARK: remove this comment and this method.
private fun createDumpData(): List<Message> {
val user1 = User("1",
"Filipe Brito",
"filipe.brito",
"online",
"https://open.rocket.chat/avatar/filipe.brito")
val user2 = User("2",
"Lucio Maciel",
"Lucio Maciel",
"busy",
"https://open.rocket.chat/avatar/lucio.maciel")
val message1 = Message(user1,
"This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!",
"https://rocket.chat/images/index/livechat.png",
LocalDateTime.now())
val message2 = Message(user2, "Great!",
"https://rocket.chat/images/index/screenshot.png",
LocalDateTime.now().plusHours(1))
return listOf(message1, message2)
}
// REMARK: The presenter should call this method.
private fun showMessageList(dataSet: List<Message>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.adapter = MessageListAdapter(this, dataSet.toMutableList()) {}
}
}
}
\ No newline at end of file
package chat.rocket.android.app.chatroom
import DateTimeHelper
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.TextView
import chat.rocket.android.R
import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.item_message.view.*
class MessageListAdapter(private val context: Context, private var dataSet: MutableList<Message>, private val listener: (Message) -> Unit) : RecyclerView.Adapter<MessageListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_message, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = dataSet[position]
holder.userAvatar.setImageURI(message.user.avatarUri)
holder.userName.text = message.user.name
holder.time.text = DateTimeHelper.getTime(message.localDatetime)
val textContent = message.textContent
if (textContent.isNullOrBlank()) {
holder.textContent.visibility = View.GONE
} else {
holder.textContent.text = textContent
}
val imageAttachmentUri = message.imageAttachmentUri
if (imageAttachmentUri.isNullOrBlank()) {
holder.imageAttachment.visibility = View.GONE
} else {
holder.imageAttachment.setImageURI(imageAttachmentUri)
holder.imageAttachment.setOnClickListener({
ImageViewer.Builder(context, listOf(imageAttachmentUri))
.hideStatusBar(false)
.show()
})
}
}
override fun getItemCount(): Int = dataSet.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: SimpleDraweeView = itemView.image_user_avatar
val userName: TextView = itemView.text_user_name
val time: TextView = itemView.text_message_time
val textContent: TextView = itemView.text_content
val imageAttachment: SimpleDraweeView = itemView.image_attachment
}
}
\ No newline at end of file
package chat.rocket.android.app.utils
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.support.annotation.ColorInt
import android.support.annotation.Nullable
import android.support.v4.graphics.ColorUtils
import com.facebook.common.internal.ByteStreams
import com.facebook.imageformat.ImageFormat
import com.facebook.imageformat.ImageFormatCheckerUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.drawable.DrawableFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.QualityInfo
import java.io.IOException
/**
* Simple decoder that can decode color images that have the following format: <color>#FF5722</color>.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java}
*/
object ColorImage {
// Custom ImageFormat for color images.
private val imageFormatColor = ImageFormat("IMAGE_FORMAT_COLOR", "color")
// XML color tag that our colors must start with.
val colorTag = "<color>"
/**
* Creates a new image format checker for [ColorImage.imageFormatColor].
*
* @return the image format checker.
*/
fun createFormatChecker(): ImageFormat.FormatChecker = ColorFormatChecker()
/**
* Creates a new decoder that can decode [ColorImage.imageFormatColor] images.
*
* @return the decoder.
*/
fun createDecoder(): ImageDecoder = ColorDecoder()
fun createDrawableFactory(): ColorDrawableFactory = ColorDrawableFactory()
/**
* Custom color format checker that verifies that the header of the file corresponds to our [ColorImage.colorTag].
*/
class ColorFormatChecker : ImageFormat.FormatChecker {
private val header = ImageFormatCheckerUtils.asciiBytes(colorTag)
override fun getHeaderSize(): Int {
return header.size
}
@Nullable override fun determineFormat(headerBytes: ByteArray, headerSize: Int): ImageFormat? {
if (headerSize > getHeaderSize()) {
if (ImageFormatCheckerUtils.startsWithPattern(headerBytes, header)) {
return imageFormatColor
}
}
return null
}
}
/**
* Custom closeable color image that holds a single color int value.
*/
class CloseableColorImage(@field:ColorInt @get:ColorInt val color: Int) : CloseableImage() {
private var isClosed = false
override fun close() {
isClosed = true
}
override fun getSizeInBytes(): Int = 0
override fun isClosed(): Boolean = isClosed
override fun getWidth(): Int = 0
override fun getHeight(): Int = 0
}
/**
* Decodes a color XML tag: <color>#rrggbb</color>.
*/
class ColorDecoder : ImageDecoder {
@Nullable override fun decode(encodedImage: EncodedImage, length: Int, qualityInfo: QualityInfo, options: ImageDecodeOptions): CloseableImage? {
try {
// Read the file as a string
val text = String(ByteStreams.toByteArray(encodedImage.inputStream))
// Check if the string matches "<color>#"
if (!text.startsWith(colorTag + "#")) {
return null
}
// Parse the int value between # and <
val startIndex = colorTag.length + 1
val endIndex = text.lastIndexOf('<')
var color = Integer.parseInt(text.substring(startIndex, endIndex), 16)
// Add the alpha component so that we actually see the color
color = ColorUtils.setAlphaComponent(color, 255)
// Return the CloseableImage
return CloseableColorImage(color)
} catch (e: IOException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e.printStackTrace()
}
// Return nothing if an error occurred
return null
}
}
/**
* Color drawable factory that is able to render a [CloseableColorImage] by creating a new [ColorDrawable] for the given color.
*/
class ColorDrawableFactory : DrawableFactory {
override fun supportsImageType(image: CloseableImage): Boolean {
// We can only handle CloseableColorImages.
return image is CloseableColorImage
}
@Nullable override fun createDrawable(image: CloseableImage): Drawable? {
// Just return a simple ColorDrawable with the given color value.
return ColorDrawable((image as CloseableColorImage).color)
}
}
}
\ No newline at end of file
package chat.rocket.android.app.utils
import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.imagepipeline.decoder.ImageDecoderConfig
/**
* Utility class to add custom decoders and drawable factories.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/CustomImageFormatConfigurator.java}
*/
object CustomImageFormatConfigurator {
fun createImageDecoderConfig() : ImageDecoderConfig {
return ImageDecoderConfig.newBuilder()
.addDecodingCapability(SvgDecoder.svgFormat, SvgDecoder.SvgFormatChecker(), SvgDecoder.Decoder())
.build()
}
fun addCustomDrawableFactories(draweeConfigBuilder: DraweeConfig.Builder) {
// We always add the color drawable factory so that it can be used for image decoder overrides.
draweeConfigBuilder.addCustomDrawableFactory(ColorImage.createDrawableFactory())
draweeConfigBuilder.addCustomDrawableFactory(SvgDecoder.SvgDrawableFactory())
}
}
\ No newline at end of file
package chat.rocket.android.app.utils
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import android.support.annotation.Nullable
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import com.facebook.imageformat.ImageFormat
import com.facebook.imageformat.ImageFormatCheckerUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.drawable.DrawableFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.QualityInfo
/**
* SVG example that defines all classes required to decode and render SVG images.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java}
*/
object SvgDecoder {
val svgFormat = ImageFormat("SVG_FORMAT", "svg")
// We do not include the closing ">" since there can be additional information.
private val headerTag = "<?xml"
private val possibleHeaderTags = arrayOf(ImageFormatCheckerUtils.asciiBytes("<svg"))
/**
* Custom SVG format checker that verifies that the header of the file corresponds to our [SvgDecoder.headerTag] or [SvgDecoder.possibleHeaderTags].
*/
class SvgFormatChecker : ImageFormat.FormatChecker {
private val header = ImageFormatCheckerUtils.asciiBytes(headerTag)
override fun getHeaderSize(): Int {
return header.size
}
@Nullable override fun determineFormat(headerBytes: ByteArray, headerSize: Int): ImageFormat? {
if (headerSize > getHeaderSize()) {
if (ImageFormatCheckerUtils.startsWithPattern(headerBytes, header)) {
return svgFormat
}
if (possibleHeaderTags.any { ImageFormatCheckerUtils.startsWithPattern(headerBytes, it) && ImageFormatCheckerUtils.indexOfPattern(headerBytes, headerBytes.size, header, header.size) > -1 }) {
return svgFormat
}
}
return null
}
}
/**
* Custom closeable SVG image that holds a single SVG.
*/
class CloseableSvgImage(val svg: SVG) : CloseableImage() {
private var isClose = false
override fun close() {
isClose = true
}
override fun getSizeInBytes(): Int = 0
override fun isClosed(): Boolean = isClose
override fun getWidth(): Int = 0
override fun getHeight(): Int = 0
}
/**
* Decodes a [SvgDecoder.svgFormat] image.
*/
class Decoder : ImageDecoder {
@Nullable override fun decode(encodedImage: EncodedImage, length: Int, qualityInfo: QualityInfo, options: ImageDecodeOptions): CloseableImage? {
try {
val svg = SVG.getFromInputStream(encodedImage.inputStream)
return CloseableSvgImage(svg)
} catch (e: SVGParseException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e.printStackTrace()
}
// Return nothing if an error occurred
return null
}
}
/**
* SVG drawable factory that creates [PictureDrawable]s for SVG images.
*/
class SvgDrawableFactory : DrawableFactory {
override fun supportsImageType(image: CloseableImage): Boolean {
return image is CloseableSvgImage
}
@Nullable override fun createDrawable(image: CloseableImage): Drawable? {
return SvgPictureDrawable((image as CloseableSvgImage).svg)
}
}
class SvgPictureDrawable(private val svg: SVG) : PictureDrawable(null) {
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
picture = svg.renderToPicture(bounds.width(), bounds.height())
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.di
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
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class AuthenticationModule {
@Provides
@PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
fun provideJob(): Job {
return Job()
}
}
package chat.rocket.android.authentication.infraestructure
import chat.rocket.common.model.Token
import chat.rocket.core.TokenRepository
class AuthTokenRepository : TokenRepository {
var savedToken: Token? = null
override fun get(): Token? {
return savedToken
}
override fun save(token: Token) {
savedToken = token
}
}
\ No newline at end of file
package chat.rocket.android.authentication.login.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment
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 LoginFragmentModule {
@Provides
fun loginView(frag: LoginFragment): LoginView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: LoginFragment): 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.login.di
import chat.rocket.android.authentication.login.ui.LoginFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class LoginFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(LoginFragmentModule::class))
abstract fun provideLoginFragment(): LoginFragment
}
package chat.rocket.android.authentication.login.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.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
fun authenticate(usernameOrEmail: String, password: String) {
when {
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it?
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()
}
}
}
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
}
fun signup() {
navigator.toSignUp()
}
}
\ No newline at end of file
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, 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.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.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...
/*
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)
}
}
}
private var isGlobalLayoutListenerSetUp = false
*/
companion object {
fun newInstance() = LoginFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
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?.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()
}
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)
enableLoginByFacebook()
enableLoginByGithub()
enableLoginByGoogle()
setupFabListener()
// Just an example: if the server allow the new users registration then show the respective interface.
setupSignUpListener()
showSignUpView(true)
// -------------------------------------------------------------------------------------------------------------------
*/
}
/*
override fun onDestroyView() {
super.onDestroyView()
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)
// }
}
override fun setupFabListener() {
// button_fab.setOnClickListener({
// button_fab.hide()
// showRemainingSocialAccountsView()
// scrollToBottom()
// })
}
override fun enableLoginByFacebook() {
button_facebook.setVisibility(true)
}
override fun enableLoginByGithub() {
button_github.setVisibility(true)
}
override fun enableLoginByGoogle() {
button_google.setVisibility(true)
}
override fun enableLoginByLinkedin() {
button_linkedin.setVisibility(true)
}
override fun enableLoginByMeteor() {
button_meteor.setVisibility(true)
}
override fun enableLoginByTwitter() {
button_twitter.setVisibility(true)
}
override fun enableLoginByGitlab() {
button_gitlab.setVisibility(true)
}
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()
}
override fun alertWrongPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
text_password.requestFocus()
}
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?.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)
}
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()
}
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)
}
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.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, internal val context: Context) {
lateinit var server: String
lateinit var usernameOrEmail: String
lateinit var password: String
fun toLogin(server: String) {
this.server = server
activity.addFragmentBackStack("loginFragment", R.id.fragment_container) {
LoginFragment.newInstance()
}
}
fun toTwoFA(usernameOrEmail: String, password: String) {
this.usernameOrEmail = usernameOrEmail
this.password = password
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance()
}
}
fun toSignUp() {
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
SignupFragment.newInstance()
}
}
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 chatRoom = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
}
activity.startActivity(chatRoom)
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.di
import chat.rocket.android.authentication.server.ui.ServerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class ServerFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(ServerFragmentModule::class))
abstract fun provideServerFragment(): ServerFragment
}
\ 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 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
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
package chat.rocket.android.authentication.server.ui
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.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.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)
}
companion object {
fun newInstance() = ServerFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
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()
}
override fun onDestroyView() {
super.onDestroyView()
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
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 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
package chat.rocket.android.authentication.signup.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.authentication.signup.ui.SignupFragment
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 SignupFragmentModule {
@Provides
fun signupView(frag: SignupFragment): SignupView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.authentication.signup.di
import chat.rocket.android.authentication.signup.ui.SignupFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class SignupFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(SignupFragmentModule::class))
abstract fun provideSignupFragment(): SignupFragment
}
package chat.rocket.android.authentication.signup.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.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup
import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
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()
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()
}
}
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, 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.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.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
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
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false)
} else {
text_new_user_agreement.setVisibility(true)
}
}
companion object {
fun newInstance() = SignupFragment()
}
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_authentication_sign_up, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
setUpNewUserAgreementListener()
button_sign_up.setOnClickListener {
presenter.signup(text_name.textContent, text_username.textContent, text_password.textContent, text_email.textContent)
}
}
override fun onDestroyView() {
super.onDestroyView()
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
override fun alertBlankName() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_name)
text_name.requestFocus()
}
override fun alertBlankUsername() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username)
text_username.requestFocus()
}
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() {
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() {
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
}
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
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 TwoFAFragmentModule {
@Provides
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.authentication.twofactor.di
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(TwoFAFragmentModule::class))
abstract fun provideTwoFAFragment(): TwoFAFragment
}
package chat.rocket.android.authentication.twofactor.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.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
fun authenticate(twoFactorAuthenticationCode: String) {
if (twoFactorAuthenticationCode.isBlank()) {
view.alertBlankTwoFactorAuthenticationCode()
} else {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
client.login(navigator.usernameOrEmail, navigator.password, twoFactorAuthenticationCode) // TODO This function returns a user token so should we save it?
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()
}
}
}
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
fun signup() {
navigator.toSignUp()
}
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
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.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.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.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 {
fun newInstance() = TwoFAFragment()
}
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_authentication_two_fa, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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()
}
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?.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)
}
}
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_two_factor_auth.isEnabled = value
}
private fun setupOnClickListener() {
button_log_in.setOnClickListener {
presenter.authenticate(text_two_factor_auth.textContent)
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.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.authentication.server.ui.ServerFragment
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 AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authentication)
AndroidInjection.inject(this)
addFragment("authenticationServerFragment", R.id.fragment_container) {
ServerFragment.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
package chat.rocket.android.core.lifecycle
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job
import javax.inject.Inject
class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
init {
owner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
jobs.cancel()
}
}
\ No newline at end of file
package chat.rocket.android.dagger
import android.app.Application
import javax.inject.Singleton
import chat.rocket.android.app.RocketChatApplication
import chat.rocket.android.dagger.module.ActivityBindingModule
import chat.rocket.android.dagger.module.ApplicationModule
import chat.rocket.android.dagger.module.ActivityBuilder
import chat.rocket.android.dagger.module.AppModule
import dagger.BindsInstance
import dagger.Component
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(ActivityBindingModule::class, ApplicationModule::class,
AndroidSupportInjectionModule::class))
interface ApplicationComponent {
@Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class])
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
fun build(): AppComponent
}
fun inject(application: RocketChatApplication)
fun inject(app: RocketChatApplication)
/*@Component.Builder
abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/
}
package chat.rocket.android.dagger.module;
import dagger.Module;
@Module
public abstract class ActivityBindingModule {
}
package chat.rocket.android.dagger.module
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.di.*
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.dagger.scope.PerActivity
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = [AuthenticationModule::class,
LoginFragmentProvider::class,
ServerFragmentProvider::class,
SignupFragmentProvider::class,
TwoFAFragmentProvider::class
])
abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector abstract fun bindMainActivity(): MainActivity
}
......@@ -3,14 +3,36 @@ package chat.rocket.android.dagger.module
import android.app.Application
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
@Module
class ApplicationModule {
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
......@@ -29,4 +51,37 @@ class ApplicationModule {
fun provideServerDao(database: RocketChatDatabase): ServerDao {
return database.serverDao()
}
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.HEADERS
}
return interceptor
}
@Provides
@Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().apply {
addInterceptor(logger)
}.build()
}
@Provides
@Singleton
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides
@Singleton
fun providePlatformLogger(): PlatformLogger {
return TimberLogger
}
}
package chat.rocket.android.dagger.scope
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class PerFragment
\ No newline at end of file
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
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)
}
}
\ No newline at end of file
package chat.rocket.android.login.ui;
import android.annotation.SuppressLint;
import android.support.v7.app.AppCompatActivity;
@SuppressLint("Registered")
public class LoginActivity extends AppCompatActivity {
}
......@@ -4,7 +4,7 @@ import chat.rocket.android.server.domain.model.Server
import chat.rocket.android.util.DataToDomain
class ServerEntityMapper : DataToDomain<ServerEntity, Server> {
override fun translate(serverEntity: ServerEntity): Server {
return Server(serverEntity.id, serverEntity.name, serverEntity.host, serverEntity.avatar)
override fun translate(data: ServerEntity): Server {
return Server(data.id, data.name, data.host, data.avatar)
}
}
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()
}
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
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 chat.rocket.android.core.lifecycle.CancelStrategy
import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
/**
* Launches a coroutine on the UI context.
*
* @param strategy a CancelStrategy for canceling the coroutine job
*/
fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = UI, parent = strategy.jobs, block = block)
}
\ 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 {
if (isEmpty()) {
return value
}
return this
}
fun View.setVisibility(value: Boolean) {
if (value) {
this.visibility = View.VISIBLE
} else {
this.visibility = 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) {
text = value
}
package chat.rocket.android.util
import chat.rocket.common.util.PlatformLogger
import timber.log.Timber
object TimberLogger : PlatformLogger {
override fun debug(s: String) {
Timber.d(s)
}
override fun info(s: String) {
Timber.i(s)
}
override fun warn(s: String) {
Timber.w(s)
}
}
\ No newline at end of file
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
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/layout_app_bar" />
</RelativeLayout>
\ No newline at end of file
......@@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme">
android:theme="@style/ChatListTheme">
<FrameLayout
android:id="@+id/fragment_container"
......
<?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
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
</android.support.design.widget.AppBarLayout>
\ 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="wrap_content"
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"
......@@ -9,14 +14,36 @@
android:layout_centerHorizontal="true"
android:text="@string/title_sign_in_your_server" />
<TextView
android:id="@+id/text_server_protocol"
style="@style/AuthenticationLabel"
android:layout_below="@id/text_headline"
android:layout_marginTop="32dp"
android:gravity="center_vertical"
android:text="@string/default_protocol" />
<EditText
android:id="@+id/text_server_url"
style="@style/AuthenticationEditText"
android:layout_below="@id/text_headline"
android:layout_marginStart="0dp"
android:layout_marginTop="32dp"
android:layout_toEndOf="@id/text_server_protocol"
android:cursorVisible="false"
android:hint="@string/default_server"
android:imeOptions="actionDone"
android:inputType="textUri"
android:text="@string/msg_https" />
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"
......
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/layout_message_composer"
android:scrollbars="vertical" />
<include
android:id="@+id/layout_message_composer"
layout="@layout/message_composer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout>
\ No newline at end of file
......@@ -15,7 +15,7 @@
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/middle_container"
app:layout_constraintRight_toLeftOf="@+id/middle_container"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
......@@ -25,8 +25,8 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toRightOf="@id/image_user_avatar"
app:layout_constraintRight_toLeftOf="@id/right_container">
app:layout_constraintLeft_toRightOf="@+id/image_user_avatar"
app:layout_constraintRight_toLeftOf="@+id/right_container">
<TextView
android:id="@+id/text_chat_name"
......
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
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">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_user_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
<LinearLayout
android:id="@+id/top_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintLeft_toRightOf="@+id/image_user_avatar">
<TextView
android:id="@+id/text_user_name"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
tools:text="Ronald Perkins" />
<TextView
android:id="@+id/text_message_time"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
tools:text="11:45 pm" />
</LinearLayout>
<TextView
android:id="@+id/text_content"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_container"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_content" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDividerMessageComposer" />
<LinearLayout
android:id="@+id/top_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="10dp"
android:orientation="horizontal"
android:paddingBottom="10dp"
app:layout_constraintTop_toBottomOf="@+id/divider">
<EditText
android:id="@+id/text_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:hint="@string/msg_message"
android:maxLines="4" />
<TextView
android:id="@+id/text_send"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:text="@string/action_send"
android:textColor="@color/colorAccent" />
</LinearLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?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>
<string name="msg_content_description_log_in_using_github">Fazer login através do Github</string>
<string name="msg_content_description_log_in_using_google">Fazer login através do Google</string>
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AuthenticationEditText" parent="Widget.AppCompat.EditText">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_marginStart">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:layout_marginEnd">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:paddingStart">@dimen/edit_text_margin</item>
<item name="android:paddingEnd">@dimen/edit_text_margin</item>
<item name="android:maxLines">1</item>
<item name="android:drawablePadding">@dimen/edit_text_drawable_padding</item>
<item name="android:drawableTint">@color/colorDrawableTintGrey</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:background">@drawable/style_edit_text</item>
</style>
</resources>
\ No newline at end of file
......@@ -4,12 +4,14 @@
<color name="colorPrimaryDark">#ff212121</color> <!-- Material Grey 900 -->
<color name="colorAccent">#FF1976D2</color> <!-- Material Blue 700 -->
<color name="colorDrawableTintGrey">#9FA2A8</color>
<color name="colorUserStatusOnline">#2FE1A8</color>
<color name="colorUserStatusBusy">#F33E5B</color>
<color name="colorUserStatusAway">#FDD236</color>
<color name="colorUserStatusOffline">#1F2228</color>
<color name="colorDrawableTintGrey">#9FA2A8</color>
<color name="colorDividerMessageComposer">#D8D8D8</color>
<color name="white">#FFFFFFFF</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="default_protocol" translatable="false">https://</string>
<string name="default_server" translatable="false">open.rocket.chat</string>
</resources>
\ No newline at end of file
......@@ -5,23 +5,30 @@
<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_https" translatable="false">https://</string>
<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>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string>
......
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.NoActionBar">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="colorControlHighlight">@color/colorPrimary</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="AuthenticationTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="AuthenticationTheme" parent="android:Theme.Material.NoActionBar.Fullscreen">
<item name="android:navigationBarColor">@color/colorPrimary</item>
<style name="ChatListTheme" parent="AppTheme">
<item name="android:windowTranslucentStatus">true</item>
<item name="colorControlHighlight">@color/colorPrimary</item> <!-- We need this item because of this FloatingSearchView's issue: https://github.com/arimorty/floatingsearchview/issues/222 -->
</style>
<!-- Widget styles. -->
......@@ -32,6 +36,18 @@
<item name="android:paddingEnd">@dimen/edit_text_margin</item>
<item name="android:maxLines">1</item>
<item name="android:drawablePadding">@dimen/edit_text_drawable_padding</item>
<item name="android:drawableTint" tools:ignore="NewApi">@color/colorDrawableTintGrey</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:background">@drawable/style_edit_text</item>
</style>
<style name="AuthenticationLabel" parent="TextAppearance.AppCompat.Medium">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_marginStart">@dimen/screen_edge_left_and_right_margins</item>
<item name="android:paddingStart">@dimen/edit_text_margin</item>
<item name="android:maxLines">1</item>
<item name="android:drawablePadding">@dimen/edit_text_drawable_padding</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:background">@drawable/style_edit_text</item>
</style>
......
......@@ -6,9 +6,11 @@ buildscript {
google()
jcenter()
}
dependencies {
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}"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
......@@ -19,6 +21,8 @@ allprojects {
repositories {
google()
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" }
}
apply from: rootProject.file('dependencies.gradle')
......
......@@ -9,6 +9,8 @@ machine:
ANDROID_HOME: /usr/local/android-sdk-linux
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
dependencies:
pre:
......@@ -28,14 +30,18 @@ dependencies:
# - echo $GOOGLE_SERVICES_BASE64 | base64 --decode > app/src/release/google-services.json
# - echo $API_KEY_STRINGS_BASE64 | base64 --decode > app/src/release/res/values/api_key_strings.xml
- mkdir -p $ANDROID_HOME/licenses/
- echo 8933bad161af4178b1185d1a37fbf41ea5269c55 >> $ANDROID_HOME/licenses/android-sdk-license
- echo d56f5187479451eabf01fb78af6dfcb131a6481e >> $ANDROID_HOME/licenses/android-sdk-license
- echo y | android update sdk --no-ui --all --filter tools,platform-tools
- echo y | android update sdk --no-ui --all --filter android-27
- echo y | android update sdk --no-ui --all --filter extra-android-m2repository,extra-android-support
- echo y | android update sdk --no-ui --all --filter extra-google-m2repository,extra-google-google_play_services
- echo y | android update sdk --no-ui --all --filter build-tools-27.0.0
#- yes | sdkmanager --licenses
cache_directories:
- /usr/local/android-sdk-linux/tools
- /usr/local/android-sdk-linux/build-tools/27.0.0
#- /usr/local/android-sdk-linux/tools
#- /usr/local/android-sdk-linux/build-tools/27.0.0
test:
override:
......
......@@ -6,63 +6,81 @@ ext {
compileSdk : 27,
targetSdk : 27,
buildTools : '27.0.0',
kotlin : '1.2.0',
kotlin : '1.2.10',
coroutine : '0.20',
dokka : '0.9.15',
kotshi : '0.3.0-beta2',
// Main dependencies
support : '27.0.0',
constraintLayout : '1.0.2',
dagger : '2.11',
room : '1.0.0-beta1',
rxjava : '2.1.4',
rxandroid : '2.0.1',
moshi : '1.5.0',
okhttp : '3.9.0',
fresco : '1.5.0',
timber : '4.5.1',
threeTenABP : '1.0.5',
floatingSearchView: '2.1.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.5.0',
frescoImageViewer : '0.5.0',
floatingSearchView : '2.1.1',
androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3',
// 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-jre7:${versions.kotlin}",
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}",
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}",
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}",
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}",
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}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
// For testing
toolsJar : files(Jvm.current().getToolsJar()),
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.kotlin
implementation libraries.appCompat
implementation libraries.exoPlayer
testImplementation libraries.junit
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.player">
<application>
<activity
android:name=".PlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:theme="@style/PlayerTheme" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android.player
import android.net.Uri
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_player.*
class PlayerActivity : AppCompatActivity() {
private lateinit var player: SimpleExoPlayer
private var isPlayerInitialized = false
private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
}
override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initializePlayer()
}
}
override fun onResume() {
super.onResume()
hideSystemUi()
if (Util.SDK_INT <= 23 || !isPlayerInitialized) {
initializePlayer()
}
}
override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23) {
releasePlayer()
}
}
override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23) {
releasePlayer()
}
}
private fun initializePlayer() {
if (!isPlayerInitialized) {
player = ExoPlayerFactory.newSimpleInstance(DefaultRenderersFactory(this), DefaultTrackSelector(), DefaultLoadControl())
player_view.player = player
player.playWhenReady = playWhenReady
player.seekTo(currentWindow, playbackPosition)
isPlayerInitialized = true
}
val uri = Uri.parse("http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4")
val mediaSource = buildMediaSource(uri)
player.prepare(mediaSource, true, false)
}
private fun releasePlayer() {
if (isPlayerInitialized) {
playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex
playWhenReady = player.playWhenReady
player.release()
isPlayerInitialized = false
}
}
private fun buildMediaSource(uri: Uri): MediaSource = ExtractorMediaSource(uri, DefaultHttpDataSourceFactory("rocket-chat-android-player"), DefaultExtractorsFactory(), null, null)
private fun hideSystemUi() {
// Read the docs for detailed explanation: https://developer.android.com/training/basics/firstapp/starting-activity.html and https://developer.android.com/design/patterns/fullscreen.html
player_view.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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