diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d30b9e1..501130e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,14 +13,19 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + + - + \ No newline at end of file diff --git a/app/src/main/java/com/treyherman/employeedirectory/InjectorModule.kt b/app/src/main/java/com/treyherman/employeedirectory/InjectorModule.kt index 1154287..dc0c0bb 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/InjectorModule.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/InjectorModule.kt @@ -1,6 +1,8 @@ package com.treyherman.employeedirectory import com.treyherman.employeedirectory.di.scope.ActivityScope +import com.treyherman.employeedirectory.scenes.employeedetail.EmployeeDetailActivity +import com.treyherman.employeedirectory.scenes.employeedetail.EmployeeDetailModule import com.treyherman.employeedirectory.scenes.maindirectory.MainDirectoryActivity import com.treyherman.employeedirectory.scenes.maindirectory.MainDirectoryModule import dagger.Module @@ -12,4 +14,8 @@ interface InjectorModule { @ActivityScope @ContributesAndroidInjector(modules = [MainDirectoryModule::class]) fun contributesMainDirectoryActivity(): MainDirectoryActivity + + @ActivityScope + @ContributesAndroidInjector(modules = [EmployeeDetailModule::class]) + fun contributesEmployeeDetailActivity(): EmployeeDetailActivity } diff --git a/app/src/main/java/com/treyherman/employeedirectory/extension/Intent.kt b/app/src/main/java/com/treyherman/employeedirectory/extension/Intent.kt new file mode 100644 index 0000000..31357ed --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/extension/Intent.kt @@ -0,0 +1,19 @@ +package com.treyherman.employeedirectory.extension + +import android.content.Intent +import android.net.Uri + +private const val TEL_INTENT = "tel: %s" + +fun createPhoneIntent(phoneNumber: String): Intent = Intent( + Intent.ACTION_DIAL, Uri.parse( + String.format( + TEL_INTENT, phoneNumber + ) + ) +) + +//fun createEmailIntent(email: String): Intent = Intent( +// Intent.ACTION_SENDTO, Uri.fromParts("mailto", email, null) +// +//) diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailActivity.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailActivity.kt new file mode 100644 index 0000000..7873054 --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailActivity.kt @@ -0,0 +1,112 @@ +package com.treyherman.employeedirectory.scenes.employeedetail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View + +import androidx.appcompat.app.AppCompatActivity +import coil.ImageLoader +import coil.load +import com.jakewharton.rxbinding3.view.clicks +import com.treyherman.employeedirectory.R +import com.treyherman.employeedirectory.extension.createPhoneIntent +import com.treyherman.employeedirectory.scenes.maindirectory.model.UIEmployee + +import dagger.android.AndroidInjection +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.addTo +import kotlinx.android.synthetic.main.activity_employee_detail.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class EmployeeDetailActivity : AppCompatActivity(), EmployeeDetailMvp.View { + + companion object { + const val KEY_EMPLOYEE_PARCELABLE = "employee" + + fun createIntent(context: Context, employee: UIEmployee): Intent = + Intent(context, EmployeeDetailActivity::class.java) + .putExtra(KEY_EMPLOYEE_PARCELABLE, employee) + } + + @Inject + lateinit var presenter: EmployeeDetailMvp.Presenter + + @Inject + lateinit var imageLoader: ImageLoader + + private val disposables = CompositeDisposable() + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_employee_detail) + setupView() + presenter.onCreate(intent.extras) + } + + override fun onDestroy() { + disposables.dispose() + super.onDestroy() + } + + override fun displayEmployee(employee: UIEmployee) { + ivEmployee.load(employee.photoUrlLarge, imageLoader) { + placeholder(R.drawable.ic_profile_placeholder) + } + tvNameAndTeam.text = employee.nameAndTeam + employee.phoneNumber?.let { + tvPhoneNumber.text = it + tvPhoneNumber.visibility = View.VISIBLE + btCall.visibility = View.VISIBLE + } ?: run { + tvPhoneNumber.visibility = View.GONE + btCall.visibility = View.GONE + } + + tvEmail.text = employee.email + + employee.phoneNumber?.let { + tvBio.text = it + tvBio.visibility = View.VISIBLE + } ?: run { tvBio.visibility = View.GONE } + + tvClassification.text = employee.classification + } + + override fun displayNonCancelableErrorDialog(message: String) { + + } + + override fun openDialScreen(phoneNumber: String) { + startActivity(createPhoneIntent(phoneNumber)) + } + + override fun openEmailScreen(email: String) { + TODO("Not yet implemented") + } + + // region private + private fun setupView() { + subscribeToCallClickEvent() + subscribeToEmailClickEvent() + } + + private fun subscribeToCallClickEvent() { + btCall.clicks() + .debounce(300L, TimeUnit.MILLISECONDS) + .subscribe { + presenter.onCallClicked() + }.addTo(disposables) + } + + private fun subscribeToEmailClickEvent() { + btEmail.clicks() + .debounce(300L, TimeUnit.MILLISECONDS) + .subscribe { + presenter.onEmailClicked() + }.addTo(disposables) + } + // endregion private +} diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailBindModule.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailBindModule.kt new file mode 100644 index 0000000..6957374 --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailBindModule.kt @@ -0,0 +1,14 @@ +package com.treyherman.employeedirectory.scenes.employeedetail + +import com.treyherman.employeedirectory.di.scope.ActivityScope + +import dagger.Binds +import dagger.Module + +@Module +interface EmployeeDetailBindModule { + + @Binds + @ActivityScope + fun bindView(activity: EmployeeDetailActivity): EmployeeDetailMvp.View +} diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailModule.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailModule.kt new file mode 100644 index 0000000..3553b84 --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailModule.kt @@ -0,0 +1,27 @@ +package com.treyherman.employeedirectory.scenes.employeedetail + +import android.content.Context +import coil.ImageLoader +import com.treyherman.employeedirectory.di.scope.ActivityScope +import com.treyherman.employeedirectory.view.image.ImageLoaderProvider +import dagger.Module +import dagger.Provides + +@Module(includes = [EmployeeDetailBindModule::class]) +class EmployeeDetailModule { + + @Provides + @ActivityScope + fun providePresenter(presenter: EmployeeDetailPresenter): EmployeeDetailMvp.Presenter { + return presenter + } + + @Provides + @ActivityScope + fun provideImageLoader( + imageLoaderProvider: ImageLoaderProvider, + context: Context + ): ImageLoader { + return imageLoaderProvider.provide(context) + } +} diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailMvp.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailMvp.kt new file mode 100644 index 0000000..ff9fa33 --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailMvp.kt @@ -0,0 +1,29 @@ +package com.treyherman.employeedirectory.scenes.employeedetail + +import android.os.Bundle +import com.treyherman.employeedirectory.scenes.maindirectory.model.UIEmployee + + +interface EmployeeDetailMvp { + interface View { + fun displayEmployee(employee: UIEmployee) + + fun displayNonCancelableErrorDialog(message: String) + + fun finish() + + fun openDialScreen(phoneNumber: String) + + fun openEmailScreen(email: String) + } + + interface Presenter { + fun onCreate(extras: Bundle?) + + fun onNonCancelableErrorDialogDismissed() + + fun onCallClicked() + + fun onEmailClicked() + } +} diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailPresenter.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailPresenter.kt new file mode 100644 index 0000000..e0dc59c --- /dev/null +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/employeedetail/EmployeeDetailPresenter.kt @@ -0,0 +1,43 @@ +package com.treyherman.employeedirectory.scenes.employeedetail + +import android.content.res.Resources +import android.os.Bundle +import com.treyherman.employeedirectory.R +import com.treyherman.employeedirectory.di.scope.ActivityScope +import com.treyherman.employeedirectory.scenes.employeedetail.EmployeeDetailActivity.Companion.KEY_EMPLOYEE_PARCELABLE +import com.treyherman.employeedirectory.scenes.maindirectory.model.UIEmployee +import javax.inject.Inject + +@ActivityScope +class EmployeeDetailPresenter @Inject constructor( + private val view: EmployeeDetailMvp.View, + private val resources: Resources +) : EmployeeDetailMvp.Presenter { + + private lateinit var employee: UIEmployee + + override fun onCreate(extras: Bundle?) { + extras?.getParcelable(KEY_EMPLOYEE_PARCELABLE)?.let { + employee = it + when (it.isValid()) { + true -> view.displayEmployee(it) + false -> view.displayNonCancelableErrorDialog(resources.getString(R.string.something_went_wrong)) + } + } + ?: view.displayNonCancelableErrorDialog(resources.getString(R.string.something_went_wrong)) + } + + override fun onNonCancelableErrorDialogDismissed() { + view.finish() + } + + override fun onCallClicked() { + employee.phoneNumber?.let { + view.openDialScreen(it) + } + } + + override fun onEmailClicked() { + view.openEmailScreen(employee.email) + } +} diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryActivity.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryActivity.kt index eed1840..6e0c6d4 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryActivity.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryActivity.kt @@ -11,6 +11,7 @@ import com.jakewharton.rxbinding3.swiperefreshlayout.refreshes import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.widget.itemSelections import com.treyherman.employeedirectory.R +import com.treyherman.employeedirectory.scenes.employeedetail.EmployeeDetailActivity import com.treyherman.employeedirectory.scenes.maindirectory.list.EmployeeAdapter import com.treyherman.employeedirectory.scenes.maindirectory.list.employee.EmployeeSubcomponent import com.treyherman.employeedirectory.scenes.maindirectory.model.DataSelectionType @@ -99,6 +100,10 @@ class MainDirectoryActivity : AppCompatActivity(), MainDirectoryMvp.View { vRefresh.isRefreshing = false } + override fun openEmployeeDetails(employee: UIEmployee) { + startActivity(EmployeeDetailActivity.createIntent(this, employee)) + } + // region private private fun setupView() { rvEmployees.adapter = rvAdapter diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryMvp.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryMvp.kt index bfbdd3d..7263750 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryMvp.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryMvp.kt @@ -15,6 +15,8 @@ interface MainDirectoryMvp { fun hideLoading() fun displayEmptyContent(message: String) + + fun openEmployeeDetails(employee: UIEmployee) } interface Presenter { @@ -27,5 +29,7 @@ interface MainDirectoryMvp { fun onTryAgainClicked(dataSelection: DataSelectionType, currentEmployees: List) fun onDataTypeSelected(dataSelection: DataSelectionType, currentEmployees: List) + + fun presentEmployeeDetails(employee: UIEmployee) } } diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryPresenter.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryPresenter.kt index 45a4ae2..a95dd83 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryPresenter.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/MainDirectoryPresenter.kt @@ -71,6 +71,10 @@ class MainDirectoryPresenter @Inject constructor( } } + override fun presentEmployeeDetails(employee: UIEmployee) { + view.openEmployeeDetails(employee) + } + // region private private fun subscribeToRefreshedDefaultEmployees(currentEmployees: List) { employeesDisposable?.dispose() diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeMvp.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeMvp.kt index b643f87..524180b 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeMvp.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeMvp.kt @@ -22,5 +22,7 @@ interface EmployeeMvp { interface Presenter { fun onBind(employee: UIEmployee) + + fun onEmployeeClicked() } } \ No newline at end of file diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeePresenter.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeePresenter.kt index 5a19d75..3602a6a 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeePresenter.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeePresenter.kt @@ -1,19 +1,25 @@ package com.treyherman.employeedirectory.scenes.maindirectory.list.employee import com.treyherman.employeedirectory.di.scope.ViewScope +import com.treyherman.employeedirectory.scenes.maindirectory.MainDirectoryMvp import com.treyherman.employeedirectory.scenes.maindirectory.model.UIEmployee import javax.inject.Inject @ViewScope class EmployeePresenter @Inject constructor( - private val view: EmployeeMvp.View + private val view: EmployeeMvp.View, + private val parentPresenter: MainDirectoryMvp.Presenter ) : EmployeeMvp.Presenter { + + private lateinit var employee: UIEmployee + override fun onBind(employee: UIEmployee) { + this.employee = employee view.displayEmployeeInfo( employee.nameAndTeam, employee.email, employee.classification, - employee.photoUrl + employee.photoUrlSmall ) employee.phoneNumber?.let { @@ -24,4 +30,8 @@ class EmployeePresenter @Inject constructor( view.displayBio(it) } ?: view.hideBio() } + + override fun onEmployeeClicked() { + parentPresenter.presentEmployeeDetails(employee) + } } diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeView.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeView.kt index c7a75ee..3fcf475 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeView.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/list/employee/EmployeeView.kt @@ -7,11 +7,14 @@ import androidx.cardview.widget.CardView import coil.ImageLoader import coil.load import coil.util.CoilUtils +import com.jakewharton.rxbinding3.view.clicks import com.treyherman.employeedirectory.R import com.treyherman.employeedirectory.scenes.maindirectory.model.UIEmployee import dagger.android.AndroidInjector +import io.reactivex.disposables.Disposable import kotlinx.android.synthetic.main.item_employee.view.* import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit import javax.inject.Inject class EmployeeView @JvmOverloads constructor( @@ -26,6 +29,8 @@ class EmployeeView @JvmOverloads constructor( @Inject lateinit var imageLoader: ImageLoader + private var clickDisposable: Disposable? = null + fun inject(injector: AndroidInjector) { injector.inject(this) } @@ -34,6 +39,16 @@ class EmployeeView @JvmOverloads constructor( presenter.onBind(employee) } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + subscribeToClickEvents() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + clickDisposable?.dispose() + } + override fun displayEmployeeInfo( nameAndTeam: String, email: String, @@ -70,5 +85,12 @@ class EmployeeView @JvmOverloads constructor( tvBiographyLabel.visibility = GONE } - + // region private + private fun subscribeToClickEvents() { + clickDisposable?.dispose() + clickDisposable = clicks() + .debounce(300L, TimeUnit.MILLISECONDS) + .subscribe { presenter.onEmployeeClicked() } + } + // endregion private } diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/mapper/EmployeeModelMapperImpl.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/mapper/EmployeeModelMapperImpl.kt index c18d77f..b82e557 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/mapper/EmployeeModelMapperImpl.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/mapper/EmployeeModelMapperImpl.kt @@ -29,6 +29,7 @@ class EmployeeModelMapperImpl @Inject constructor( response.emailAddress, response.biography, response.photoUrlSmall, + response.photoUrlLarge, mapEmployeeClassificationText(response.type) ) } diff --git a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/model/UIEmployee.kt b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/model/UIEmployee.kt index 3767151..fbf3ced 100644 --- a/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/model/UIEmployee.kt +++ b/app/src/main/java/com/treyherman/employeedirectory/scenes/maindirectory/model/UIEmployee.kt @@ -1,39 +1,55 @@ package com.treyherman.employeedirectory.scenes.maindirectory.model +import android.os.Parcel +import android.os.Parcelable + data class UIEmployee( val uuid: String, val nameAndTeam: String, val phoneNumber: String?, val email: String, val bio: String?, - val photoUrl: String?, + val photoUrlSmall: String?, + val photoUrlLarge: String?, val classification: String -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString() ?: "", + parcel.readString() ?: "", + parcel.readString(), + parcel.readString() ?: "", + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() ?: "" + ) - other as UIEmployee + fun isValid(): Boolean { + return uuid.isNotEmpty() && nameAndTeam.isNotEmpty() && email.isNotEmpty() && classification.isNotEmpty() + } - if (uuid != other.uuid) return false - if (nameAndTeam != other.nameAndTeam) return false - if (phoneNumber != other.phoneNumber) return false - if (email != other.email) return false - if (bio != other.bio) return false - if (photoUrl != other.photoUrl) return false - if (classification != other.classification) return false + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(uuid) + parcel.writeString(nameAndTeam) + parcel.writeString(phoneNumber) + parcel.writeString(email) + parcel.writeString(bio) + parcel.writeString(photoUrlSmall) + parcel.writeString(photoUrlLarge) + parcel.writeString(classification) + } - return true + override fun describeContents(): Int { + return 0 } - override fun hashCode(): Int { - var result = uuid.hashCode() - result = 31 * result + nameAndTeam.hashCode() - result = 31 * result + (phoneNumber?.hashCode() ?: 0) - result = 31 * result + email.hashCode() - result = 31 * result + (bio?.hashCode() ?: 0) - result = 31 * result + (photoUrl?.hashCode() ?: 0) - result = 31 * result + classification.hashCode() - return result + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): UIEmployee { + return UIEmployee(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } } } diff --git a/app/src/main/res/layout/activity_employee_detail.xml b/app/src/main/res/layout/activity_employee_detail.xml new file mode 100644 index 0000000..cc5d3c9 --- /dev/null +++ b/app/src/main/res/layout/activity_employee_detail.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + +