Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fossasia.openevent.general.auth

import io.reactivex.Completable
import io.reactivex.Single
import org.fossasia.openevent.general.auth.change.ChangeRequestToken
import org.fossasia.openevent.general.auth.change.ChangeRequestTokenResponse
Expand All @@ -11,6 +12,7 @@ import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.DELETE

interface AuthApi {

Expand Down Expand Up @@ -46,4 +48,7 @@ interface AuthApi {

@PATCH("auth/reset-password")
fun resetPassword(@Body requestPasswordReset: RequestPasswordReset): Single<ResetPasswordResponse>

@DELETE("users/{id}")
fun deleteAccount(@Path("id") userId: Long): Completable
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class AuthService(
}
}

fun checkPasswordValid(email: String, password: String): Single<LoginResponse> =
authApi.login(Login(email, password))

fun signUp(signUp: SignUp): Single<User> {
val email = signUp.email
val password = signUp.password
Expand All @@ -55,6 +58,8 @@ class AuthService(

fun isLoggedIn() = authHolder.isLoggedIn()

fun getId() = authHolder.getId()

fun logout(): Completable {
return Completable.fromAction {
authHolder.token = null
Expand All @@ -64,6 +69,9 @@ class AuthService(
}
}

fun deleteProfile(userId: Long = authHolder.getId()) =
authApi.deleteAccount(userId)

fun getProfile(id: Long = authHolder.getId()): Single<User> {
return userDao.getUser(id)
.onErrorResumeNext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class LoginViewModel(
val requestTokenSuccess: LiveData<Boolean> = mutableRequestTokenSuccess
private val mutableLoggedIn = SingleLiveEvent<Boolean>()
var loggedIn: LiveData<Boolean> = mutableLoggedIn
private val mutableValidPassword = MutableLiveData<Boolean>()
val validPassword: LiveData<Boolean> = mutableValidPassword

fun isLoggedIn() = authService.isLoggedIn()

Expand All @@ -50,6 +52,20 @@ class LoginViewModel(
})
}

fun checkValidPassword(email: String, password: String) {
compositeDisposable += authService.checkPasswordValid(email, password)
.withDefaultSchedulers()
.doOnSubscribe {
mutableProgress.value = true
}.doFinally {
mutableProgress.value = false
}.subscribe({
mutableValidPassword.value = true
}, {
mutableValidPassword.value = false
})
}

fun sendResetPasswordEmail(email: String) {
if (!isConnected()) return
compositeDisposable += authService.sendResetPasswordEmail(email)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import kotlinx.android.synthetic.main.dialog_change_password.view.newPassword
import kotlinx.android.synthetic.main.dialog_change_password.view.confirmNewPassword
import kotlinx.android.synthetic.main.dialog_change_password.view.textInputLayoutConfirmNewPassword
import kotlinx.android.synthetic.main.dialog_change_password.view.textInputLayoutNewPassword
import kotlinx.android.synthetic.main.dialog_confirm_delete_account.view.password
import kotlinx.android.synthetic.main.dialog_delete_account.view.confirmEmailButton
import kotlinx.android.synthetic.main.dialog_delete_account.view.confirmEmailLayout
import kotlinx.android.synthetic.main.dialog_delete_account.view.confirmDeleteCheckbox
import kotlinx.android.synthetic.main.dialog_delete_account.view.confirmDeleteLayout
import kotlinx.android.synthetic.main.dialog_delete_account.view.deleteButton
import kotlinx.android.synthetic.main.dialog_delete_account.view.cancelButton
import kotlinx.android.synthetic.main.dialog_delete_account.view.email
import kotlinx.android.synthetic.main.fragment_profile.view.login
import kotlinx.android.synthetic.main.fragment_profile.view.logout
import kotlinx.android.synthetic.main.fragment_profile.view.accountInfoContainer
Expand All @@ -35,6 +43,7 @@ import kotlinx.android.synthetic.main.fragment_profile.view.manageEvents
import kotlinx.android.synthetic.main.fragment_profile.view.settings
import kotlinx.android.synthetic.main.fragment_profile.view.ticketIssues
import kotlinx.android.synthetic.main.fragment_profile.view.changePassword
import kotlinx.android.synthetic.main.fragment_profile.view.deleteAccount
import org.fossasia.openevent.general.BuildConfig
import org.fossasia.openevent.general.CircleTransform
import org.fossasia.openevent.general.PLAY_STORE_BUILD_FLAVOR
Expand All @@ -56,6 +65,7 @@ const val PROFILE_FRAGMENT = "profileFragment"
class ProfileFragment : Fragment(), BottomIconDoubleClick {
private val profileViewModel by viewModel<ProfileViewModel>()
private val smartAuthViewModel by viewModel<SmartAuthViewModel>()
private val loginViewModel by viewModel<LoginViewModel>()

private lateinit var rootView: View
private var emailSettings: String = ""
Expand Down Expand Up @@ -111,6 +121,18 @@ class ProfileFragment : Fragment(), BottomIconDoubleClick {
profileViewModel.mutableMessage.postValue(null)
})

profileViewModel.accountDeleted
.nonNull()
.observe(viewLifecycleOwner, Observer { accountDeleted ->
if (accountDeleted) {
rootView.snackbar(getString(R.string.success_deleting_account_message))
profileViewModel.logout()
redirectToEventsFragment()
} else {
openDeleteAccountFailDialog()
}
})

profileViewModel.user
.nonNull()
.observe(viewLifecycleOwner, Observer {
Expand Down Expand Up @@ -182,6 +204,92 @@ class ProfileFragment : Fragment(), BottomIconDoubleClick {
rootView.changePassword.setOnClickListener {
handleChangePassword()
}

rootView.deleteAccount.setOnClickListener {
openDeleteAccountDialog()
}
}

private fun openDeleteAccountFailDialog() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.delete_account))
.setMessage(getString(R.string.delete_account_fail_message))
.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
dialog.cancel()
}.create()
.show()
}

private fun openDeleteAccountDialog() {
val deleteLayout = layoutInflater.inflate(R.layout.dialog_delete_account, null)
deleteLayout.confirmEmailLayout.isVisible = true

val dialog = AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.delete_account))
.setView(deleteLayout)
.create()

deleteLayout.cancelButton.setOnClickListener {
dialog.cancel()
}

deleteLayout.confirmEmailButton.setOnClickListener {
deleteLayout.confirmDeleteLayout.isVisible = true
deleteLayout.confirmEmailLayout.isVisible = false
}

deleteLayout.deleteButton.setOnClickListener {
dialog.cancel()
confirmDeleteAccount()
}

deleteLayout.confirmDeleteCheckbox.setOnCheckedChangeListener { _, isChecked ->
deleteLayout.deleteButton.isEnabled = isChecked
}

deleteLayout.email.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
deleteLayout.confirmEmailButton.isEnabled = user?.email == s.toString()
}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { /* Do Nothing */ }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { /* Do Nothing */ }
})

dialog.show()
}

private fun confirmDeleteAccount() {
val progressDialog = progressDialog(context, getString(R.string.deleting_account))

loginViewModel.progress
.nonNull()
.observe(viewLifecycleOwner, Observer {
progressDialog.show(it)
})

loginViewModel.validPassword
.nonNull()
.observe(viewLifecycleOwner, Observer {
if (it)
profileViewModel.deleteProfile()
else
rootView.snackbar(getString(R.string.invalid_password_to_delete_account_message))
})

val layout = layoutInflater.inflate(R.layout.dialog_confirm_delete_account, null)

AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.delete_account))
.setView(layout)
.setPositiveButton(getString(R.string.confirm)) { _, _ ->
user?.email?.let {
loginViewModel.checkValidPassword(it, layout.password.text.toString())
}
}.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}.create()
.show()
}

private fun handleChangePassword() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import io.reactivex.rxkotlin.plusAssign
import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers
import org.fossasia.openevent.general.common.SingleLiveEvent
import org.fossasia.openevent.general.data.Resource
import org.fossasia.openevent.general.event.EventService
import timber.log.Timber

class ProfileViewModel(private val authService: AuthService, private val resource: Resource) : ViewModel() {
class ProfileViewModel(
private val authService: AuthService,
private val resource: Resource,
private val eventService: EventService
) : ViewModel() {

private val compositeDisposable = CompositeDisposable()

Expand All @@ -25,6 +30,8 @@ class ProfileViewModel(private val authService: AuthService, private val resourc
val updatedUser: LiveData<User> = mutableUpdatedUser
private val mutableUpdatedPassword = MutableLiveData<String>()
val updatedPassword: LiveData<String> = mutableUpdatedPassword
private val mutableAccountDeleted = MutableLiveData<Boolean>()
val accountDeleted: LiveData<Boolean> = mutableAccountDeleted

fun isLoggedIn() = authService.isLoggedIn()

Expand All @@ -38,6 +45,23 @@ class ProfileViewModel(private val authService: AuthService, private val resourc
}
}

fun deleteProfile() {
compositeDisposable += authService.deleteProfile()
.withDefaultSchedulers()
.doOnSubscribe {
mutableProgress.value = true
}.doFinally {
mutableProgress.value = false
}.subscribe({
mutableAccountDeleted.value = true
mutableMessage.value = resource.getString(R.string.success_deleting_account_message)
logout()
}, {
mutableAccountDeleted.value = false
mutableMessage.value = resource.getString(R.string.error_deleting_account_message)
})
}

fun changePassword(oldPassword: String, newPassword: String) {
compositeDisposable += authService.changePassword(oldPassword, newPassword)
.withDefaultSchedulers()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ val viewModelModule = module {
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { EventsViewModel(get(), get(), get(), get(), get()) }
viewModel { StartupViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { ProfileViewModel(get(), get()) }
viewModel { ProfileViewModel(get(), get(), get()) }
viewModel { SignUpViewModel(get(), get(), get()) }
viewModel {
EventDetailsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import retrofit2.http.Query

interface EventTopicApi {

@GET("event-topics/{id}/events?include=event-topic")
fun getEventsUnderTopicId(@Path("id") id: Long): Single<List<Event>>

@GET("event-topics/{id}/events?include=event-topic")
fun getEventsUnderTopicIdPaged(
@Path("id") id: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.fossasia.openevent.general.notification

import io.reactivex.Completable
import io.reactivex.Single
import retrofit2.http.DELETE
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
Expand All @@ -18,7 +16,4 @@ interface NotificationApi {
@Path("notification_id") notificationId: Int,
@Body notification: Notification
): Single<Notification>

@DELETE("notifications/{notification_id}")
fun deleteNotification(@Path("notification_id") notificationId: Long): Completable
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ interface SpeakerApi {
@GET("sessions/{sessionId}/speakers")
fun getSpeakersForSession(@Path("sessionId") id: Long): Single<List<Speaker>>

@GET("speakers/{speaker_id}")
fun getSpeakerWithId(@Path("speaker_id") id: Long): Single<Speaker>

@GET("users/{user_id}/speakers?include=event,user")
fun getSpeakerForUser(
@Path("user_id") userId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.fossasia.openevent.general.speakers

import androidx.lifecycle.LiveData
import io.reactivex.Flowable
import io.reactivex.Single
import org.fossasia.openevent.general.auth.User
Expand Down Expand Up @@ -40,10 +39,6 @@ class SpeakerService(
speakerDao.insertSpeaker(it)
}

fun fetchSpeakersFromDb(id: Long): LiveData<List<Speaker>> {
return speakerWithEventDao.getSpeakerWithEventId(id)
}

fun fetchSpeakerForSession(sessionId: Long): Single<List<Speaker>> =
speakerApi.getSpeakersForSession(sessionId)
.doOnSuccess {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.fossasia.openevent.general.sponsor

import androidx.lifecycle.LiveData
import io.reactivex.Single

class SponsorService(
Expand All @@ -16,7 +15,4 @@ class SponsorService(
}
}
}

fun fetchSponsorsFromDb(id: Long): LiveData<List<Sponsor>> =
sponsorWithEventDao.getSponsorWithEventId(id)
}
25 changes: 25 additions & 0 deletions app/src/main/res/layout/dialog_confirm_delete_account.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding_large">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enter_password_confirm_account"
android:layout_marginBottom="@dimen/layout_margin_medium"/>

<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/enter_account_password_message"
android:layout_marginBottom="@dimen/details_margin_small">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
Loading