global: fix forgot-password

This commit is contained in:
Emin 2024-10-14 11:32:31 +05:00
parent 93c0970275
commit 97e162707b
20 changed files with 396 additions and 22 deletions

View file

@ -30,9 +30,11 @@ class AuthActivity : ComponentActivity() {
lifecycleScope.launch {
placesRepository.downloadAllData()
}
val blackest = resources.getColor(R.color.button_text) // yes, I know
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.dark(resources.getColor(R.color.black_primary)),
navigationBarStyle = SystemBarStyle.dark(resources.getColor(R.color.black_primary))
statusBarStyle = SystemBarStyle.dark(blackest),
navigationBarStyle = SystemBarStyle.dark(blackest)
)
setContent {
OrganicMapsTheme() {

View file

@ -56,7 +56,7 @@ fun Modifier.drawOverlayForTextBehind() =
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.8f),
Color.Black.copy(alpha = 0.9f),
)
)
)

View file

@ -0,0 +1,3 @@
package app.tourism.data.dto.auth
data class EmailBodyDto(val email: String)

View file

@ -5,6 +5,7 @@ import app.tourism.data.dto.CategoryDto
import app.tourism.data.dto.FavoritesDto
import app.tourism.data.dto.FavoritesIdsDto
import app.tourism.data.dto.auth.AuthResponseDto
import app.tourism.data.dto.auth.EmailBodyDto
import app.tourism.data.dto.place.ReviewDto
import app.tourism.data.dto.place.ReviewIdsDto
import app.tourism.data.dto.place.ReviewsDto
@ -48,6 +49,9 @@ interface TourismApi {
@POST("logout")
suspend fun signOut(): Response<SimpleResponse>
@POST("forgot-password")
suspend fun sendEmailForPasswordReset(@Body emailBody: EmailBodyDto): Response<SimpleResponse>
// endregion auth
// region profile

View file

@ -1,6 +1,7 @@
package app.tourism.data.repositories
import android.content.Context
import app.tourism.data.dto.auth.EmailBodyDto
import app.tourism.data.remote.TourismApi
import app.tourism.data.remote.handleGenericCall
import app.tourism.domain.models.SimpleResponse
@ -42,4 +43,12 @@ class AuthRepository(private val api: TourismApi, private val context: Context)
context
)
}
fun sendEmailForPasswordReset(email: String) = flow {
handleGenericCall(
call = { api.sendEmailForPasswordReset(EmailBodyDto(email)) },
mapper = { it },
context
)
}
}

View file

@ -0,0 +1,60 @@
package app.tourism.ui.screens.auth.sign_in
import android.content.Context
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.organicmaps.R
import app.tourism.data.repositories.AuthRepository
import app.tourism.domain.models.SimpleResponse
import app.tourism.domain.models.resource.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ForgotPasswordViewModel @Inject constructor(
@ApplicationContext val context: Context,
private val authRepository: AuthRepository,
) : ViewModel() {
private val uiChannel = Channel<ForgotPasswordUiEvent>()
val uiEventsChannelFlow = uiChannel.receiveAsFlow()
private val _email = MutableStateFlow("")
val email = _email.asStateFlow()
fun setEmail(value: String) {
_email.value = value
}
private val _forgotPasswordResponse =
MutableStateFlow<Resource<SimpleResponse>>(Resource.Idle())
val forgotPasswordResponse = _forgotPasswordResponse.asStateFlow()
fun sendEmailForPasswordReset() {
viewModelScope.launch {
authRepository.sendEmailForPasswordReset(email.value)
.collectLatest { resource ->
_forgotPasswordResponse.value = resource
if (resource is Resource.Success) {
uiChannel.send(ForgotPasswordUiEvent.PopDialog)
uiChannel.send(ForgotPasswordUiEvent.ShowToast(context.getString(R.string.we_sent_you_password_reset_email)))
} else if (resource is Resource.Error) {
uiChannel.send(ForgotPasswordUiEvent.ShowToast(resource.message ?: ""))
}
}
}
}
}
sealed interface ForgotPasswordUiEvent {
data object PopDialog : ForgotPasswordUiEvent
data class ShowToast(val message: String) : ForgotPasswordUiEvent
}

View file

@ -1,12 +1,12 @@
package app.tourism.ui.screens.auth.sign_in
import PasswordEditText
import android.view.RoundedCorner
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -17,11 +17,14 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
@ -31,16 +34,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
import app.organicmaps.R
import app.tourism.BASE_URL
import app.tourism.Constants
import app.tourism.domain.models.resource.Resource
import app.tourism.drawDarkContainerBehind
import app.tourism.drawOverlayForTextBehind
import app.tourism.ui.ObserveAsEvents
import app.tourism.ui.common.HorizontalSpace
import app.tourism.ui.common.VerticalSpace
import app.tourism.ui.common.buttons.PrimaryButton
import app.tourism.ui.common.buttons.SecondaryButton
import app.tourism.ui.common.nav.BackButton
import app.tourism.ui.common.textfields.AuthEditText
import app.tourism.ui.theme.TextStyles
@ -56,7 +62,9 @@ fun SignInScreen(
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val userName = vm.email.collectAsState().value
var showForgotPasswordDialog by remember { mutableStateOf(false) }
val email = vm.email.collectAsState().value
val password = vm.password.collectAsState().value
val signInResponse = vm.signInResponse.collectAsState().value
@ -105,7 +113,7 @@ fun SignInScreen(
)
VerticalSpace(height = 32.dp)
AuthEditText(
value = userName,
value = email,
onValueChange = { vm.setEmail(it) },
hint = stringResource(id = R.string.email),
keyboardActions = KeyboardActions(
@ -134,10 +142,7 @@ fun SignInScreen(
TextButton(
onClick = {
openUrlInBrowser(
context,
"$BASE_URL/forgot-password"
)
showForgotPasswordDialog = true
},
) {
Text(
@ -161,6 +166,87 @@ fun SignInScreen(
color = Color.White,
style = TextStyles.h4.copy()
)
if (showForgotPasswordDialog) {
Dialog(
onDismissRequest = {
showForgotPasswordDialog = false
},
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
ForgotPasswordDialog(dismissDialog = {
showForgotPasswordDialog = false
})
}
}
}
}
@Composable
fun ForgotPasswordDialog(
vm: ForgotPasswordViewModel = hiltViewModel(),
dismissDialog: () -> Unit
) {
val context = LocalContext.current
val email = vm.email.collectAsState().value
val forgotPasswordResponse = vm.forgotPasswordResponse.collectAsState().value
ObserveAsEvents(flow = vm.uiEventsChannelFlow) { event ->
when (event) {
is ForgotPasswordUiEvent.PopDialog -> dismissDialog()
is ForgotPasswordUiEvent.ShowToast -> context.showToast(event.message)
}
}
Column(
modifier = Modifier
.padding(16.dp)
.clip(RoundedCornerShape(16.dp))
.background(color = Color.Black)
.padding(32.dp)
) {
Text(
text = stringResource(R.string.send_email_for_password_reset),
style = TextStyles.h3,
color = Color.White,
textAlign = TextAlign.Center
)
AuthEditText(
value = email,
onValueChange = { vm.setEmail(it) },
hint = stringResource(id = R.string.email),
keyboardActions = KeyboardActions(
onDone = {
vm.sendEmailForPasswordReset()
},
),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
)
VerticalSpace(32.dp)
Row {
PrimaryButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
label = stringResource(id = R.string.send),
isLoading = forgotPasswordResponse is Resource.Loading,
onClick = {
vm.sendEmailForPasswordReset()
},
)
HorizontalSpace(16.dp)
SecondaryButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
label = stringResource(id = R.string.cancel),
onClick = dismissDialog,
)
}
}
}

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="359dp"
android:height="466dp"
android:viewportWidth="359"
android:viewportHeight="466">
<path
android:pathData="M20,0L339,0A20,20 0,0 1,359 20L359,446A20,20 0,0 1,339 466L20,466A20,20 0,0 1,0 446L0,20A20,20 0,0 1,20 0z"
android:fillColor="#ffffff"
android:fillAlpha="0.1"/>
</vector>

View file

@ -450,6 +450,8 @@
<string name="login_osm">Войти в OpenStreetMap</string>
<string name="password">Пароль</string>
<string name="forgot_password">Забыли пароль?</string>
<string name="send_email_for_password_reset">Отправьте свой email, чтобы мы отправили вам ссылку для восстановления пароля</string>
<string name="we_sent_you_password_reset_email">Мы отправили вам письмо для восстановления пароля</string>
<string name="logout">Выйти</string>
<string name="edit_place">Редактировать место</string>
<string name="add_language">Добавить язык</string>

View file

@ -472,6 +472,8 @@
<string name="login_osm">Login to OpenStreetMap</string>
<string name="password">Password</string>
<string name="forgot_password">Forgot your password?</string>
<string name="send_email_for_password_reset">Send your email, so you receive a link for password reset</string>
<string name="we_sent_you_password_reset_email">We sent you email for password reset\n</string>
<string name="logout">Log Out</string>
<string name="edit_place">Edit Place</string>
<string name="add_language">Add a language</string>

View file

@ -3981,6 +3981,10 @@
"tourism_forgot_password" = "Forgot password?";
"send_email_for_password_reset" = "Send your email, so you receive a link for password reset";
"we_sent_you_password_reset_email" = "We sent you email for password reset";
"home" = "Home";
"favorites" = "Favorites";

View file

@ -3981,6 +3981,10 @@
"tourism_forgot_password" = "Forgot password?";
"send_email_for_password_reset" = "Send your email, so you receive a link for password reset";
"we_sent_you_password_reset_email" = "We sent you email for password reset";
"home" = "Home";
"favorites" = "Favorites";

View file

@ -3981,6 +3981,10 @@
"tourism_forgot_password" = "Забыли пароль?";
"send_email_for_password_reset" = "Отправьте свой email, чтобы мы отправили вам ссылку для восстановления пароля";
"we_sent_you_password_reset_email" = "Мы отправили вам письмо для восстановления пароля";
"home" = "Главная";
"favorites" = "Избранное";

View file

@ -590,6 +590,8 @@
CE6450202C9402EC0075A59B /* ReviewsPersistenceControllerTesterBro.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE64501F2C9402EC0075A59B /* ReviewsPersistenceControllerTesterBro.swift */; };
CE6450242C9772310075A59B /* DownloadProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6450232C9772310075A59B /* DownloadProgress.swift */; };
CE6450282C99572F0075A59B /* ImageStoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6450272C99572F0075A59B /* ImageStoreUtils.swift */; };
CE8982032CB9588E00FC2D2E /* EmailBodyDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8982022CB9588E00FC2D2E /* EmailBodyDto.swift */; };
CE8982052CBCD46300FC2D2E /* ForgotPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8982042CBCD46300FC2D2E /* ForgotPasswordViewController.swift */; };
CEA45BC42C9AE01000ABE6B2 /* DataSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA45BC32C9AE01000ABE6B2 /* DataSyncer.swift */; };
CED0E00E2C8ACBCA008C61CA /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = CED0E00D2C8ACBCA008C61CA /* SDWebImageSwiftUI */; };
CED0E0112C8ACBE1008C61CA /* CountryPickerView in Frameworks */ = {isa = PBXBuildFile; productRef = CED0E0102C8ACBE1008C61CA /* CountryPickerView */; };
@ -1645,6 +1647,8 @@
CE64501F2C9402EC0075A59B /* ReviewsPersistenceControllerTesterBro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsPersistenceControllerTesterBro.swift; sourceTree = "<group>"; };
CE6450232C9772310075A59B /* DownloadProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadProgress.swift; sourceTree = "<group>"; };
CE6450272C99572F0075A59B /* ImageStoreUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStoreUtils.swift; sourceTree = "<group>"; };
CE8982022CB9588E00FC2D2E /* EmailBodyDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailBodyDto.swift; sourceTree = "<group>"; };
CE8982042CBCD46300FC2D2E /* ForgotPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgotPasswordViewController.swift; sourceTree = "<group>"; };
CEA45BC32C9AE01000ABE6B2 /* DataSyncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSyncer.swift; sourceTree = "<group>"; };
CED0E0162C8ACF0D008C61CA /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = "<group>"; };
CED0E0182C8AD57C008C61CA /* EmptyUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUI.swift; sourceTree = "<group>"; };
@ -3158,6 +3162,7 @@
5260D3D72C64F8BC00C673B4 /* AuthResponseDTO.swift */,
52ED919E2C71F718000EE25B /* SignUpRequestDTO.swift */,
529A5F3E2C86E09B004FE4A1 /* HashDTO.swift */,
CE8982022CB9588E00FC2D2E /* EmailBodyDto.swift */,
);
path = Auth;
sourceTree = "<group>";
@ -3468,6 +3473,7 @@
52E2D3A32C59F9CE00A8843A /* WelcomeViewController.swift */,
52B573EB2C61E1C10047FAC9 /* SignInViewController.swift */,
52B573F12C61E8980047FAC9 /* SignUpViewController.swift */,
CE8982042CBCD46300FC2D2E /* ForgotPasswordViewController.swift */,
);
path = Screens;
sourceTree = "<group>";
@ -5242,6 +5248,7 @@
99C9642B2428C0F700E41723 /* PlacePageHeaderViewController.swift in Sources */,
F6FE3C391CC50FFD00A73196 /* MWMPlaceDoesntExistAlert.m in Sources */,
F6E2FDFE1E097BA00083EBEC /* MWMOpeningHoursClosedSpanTableViewCell.mm in Sources */,
CE8982032CB9588E00FC2D2E /* EmailBodyDto.swift in Sources */,
34B846A12029DCC10081ECCD /* BMCCategoriesHeader.swift in Sources */,
99A614D523C8911A00D8D8D0 /* AuthStyleSheet.swift in Sources */,
99A906F123FA946E0005872B /* DifficultyViewRenderer.swift in Sources */,
@ -5450,6 +5457,7 @@
3486B5191E27AD3B0069C126 /* MWMFrameworkListener.mm in Sources */,
3404756B1E081A4600C92850 /* MWMSearch+CoreSpotlight.mm in Sources */,
CD9AD96C2281B56900EC174A /* CPViewPortState.swift in Sources */,
CE8982052CBCD46300FC2D2E /* ForgotPasswordViewController.swift in Sources */,
EDE243DD2B6D2E640057369B /* AboutController.swift in Sources */,
3404755C1E081A4600C92850 /* MWMLocationManager.mm in Sources */,
3454D7BC1E07F045004AF2AD /* CLLocation+Mercator.mm in Sources */,

View file

@ -5,6 +5,7 @@ struct APIEndpoints {
static let signInUrl = "\(BASE_URL)login"
static let signUpUrl = "\(BASE_URL)register"
static let signOutUrl = "\(BASE_URL)logout"
static let forgotPassword = "\(BASE_URL)forgot-password"
// MARK: - Profile
static let getUserUrl = "\(BASE_URL)user"

View file

@ -0,0 +1,5 @@
import Foundation
struct EmailBodyDto: Codable {
let email: String
}

View file

@ -5,6 +5,7 @@ protocol AuthService {
func signIn(body: SignInRequestDTO) -> AnyPublisher<AuthResponseDTO, ResourceError>
func signUp(body: SignUpRequestDTO) -> AnyPublisher<AuthResponseDTO, ResourceError>
func signOut() -> AnyPublisher<SimpleResponse, ResourceError>
func sendEmailForPasswordReset(email: String) -> AnyPublisher<SimpleResponse, ResourceError>
}
class AuthServiceImpl: AuthService {
@ -20,4 +21,8 @@ class AuthServiceImpl: AuthService {
func signOut() -> AnyPublisher<SimpleResponse, ResourceError> {
return CombineNetworkHelper.postWithoutBody(path: APIEndpoints.signOutUrl)
}
func sendEmailForPasswordReset(email: String) -> AnyPublisher<SimpleResponse, ResourceError> {
return CombineNetworkHelper.post(path: APIEndpoints.forgotPassword, body: EmailBodyDto(email: email))
}
}

View file

@ -23,4 +23,9 @@ class AuthRepositoryImpl: AuthRepository {
return authService.signOut()
.eraseToAnyPublisher()
}
func sendEmailForPasswordReset(email: String) -> AnyPublisher<SimpleResponse, ResourceError> {
return authService.sendEmailForPasswordReset(email: email)
.eraseToAnyPublisher()
}
}

View file

@ -5,4 +5,5 @@ protocol AuthRepository {
func signIn(body: SignInRequest) -> AnyPublisher<AuthResponse, ResourceError>
func signUp(body: SignUpRequest) -> AnyPublisher<AuthResponse, ResourceError>
func signOut() -> AnyPublisher<SimpleResponse, ResourceError>
func sendEmailForPasswordReset(email: String) -> AnyPublisher<SimpleResponse, ResourceError>
}

View file

@ -0,0 +1,179 @@
import UIKit
import Combine
class ForgotPasswordViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
private var authRepository = AuthRepositoryImpl(authService: AuthServiceImpl())
private let backButton: BackButton = {
let backButton = BackButton()
backButton.translatesAutoresizingMaskIntoConstraints = false
return backButton
}()
private let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "splash_background")
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let containerView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
return view
}()
private let blurView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .light)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.layer.cornerRadius = 16
blurView.clipsToBounds = true
return blurView
}()
private let titleLabel: UILabel = {
let label = UILabel()
label.text = L("send_email_for_password_reset")
UIKitFont.applyStyle(to: label, style: UIKitFont.h3)
label.textColor = .white
label.textAlignment = .center
label.numberOfLines = 2
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let emailTextField: AuthTextField = {
let textField = AuthTextField()
textField.placeholder = L("tourism_email")
textField.keyboardType = .emailAddress
textField.autocapitalizationType = .none
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private lazy var sendButton: AppButton = {
let button = AppButton(
label: L("send"),
isPrimary: true,
icon: nil,
target: self,
action: #selector(sendButtonTapped)
)
return button
}()
private lazy var cancelButton: AppButton = {
let button = AppButton(
label: L("cancel"),
isPrimary: false,
icon: nil,
target: self,
action: #selector(cancelButtonTapped)
)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func setupViews() {
view.addSubview(backgroundImageView)
view.addSubview(backButton)
view.addSubview(containerView)
containerView.addSubview(blurView)
containerView.addSubview(titleLabel)
containerView.addSubview(emailTextField)
containerView.addSubview(sendButton)
containerView.addSubview(cancelButton)
NSLayoutConstraint.activate([
// Background Image
backgroundImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
backgroundImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
backgroundImageView.topAnchor.constraint(equalTo: view.topAnchor),
backgroundImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// Back Button
backButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
backButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
// Container View
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -120),
// Blur View
blurView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
blurView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
blurView.topAnchor.constraint(equalTo: containerView.topAnchor),
blurView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
// Title Label
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 32),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 32),
titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -32),
// Email Text Field
emailTextField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 32),
emailTextField.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
emailTextField.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
// Send Button
sendButton.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 32),
sendButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
sendButton.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.43),
sendButton.heightAnchor.constraint(equalToConstant: 44),
// Cancell Button
cancelButton.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 32),
cancelButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
cancelButton.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.43),
cancelButton.heightAnchor.constraint(equalToConstant: 44),
containerView.bottomAnchor.constraint(equalTo: sendButton.bottomAnchor, constant: 32)
])
backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
}
// MARK: - buttons listeners
@objc private func backButtonTapped() {
self.navigationController?.popViewController(animated: false)
}
@objc private func sendButtonTapped() {
sendButton.isLoading = true
authRepository.sendEmailForPasswordReset(email: emailTextField.text ?? "")
.sink(receiveCompletion: { [weak self] completion in
switch completion {
case .finished:
self?.showToast(message: L("we_sent_you_password_reset_email"))
self?.sendButton.isLoading = false
case .failure(let error):
self?.showError(message: error.errorDescription)
}
}, receiveValue: { _ in }
)
.store(in: &cancellables)
}
@objc private func cancelButtonTapped() {
self.navigationController?.popViewController(animated: false)
}
// MARK: - other functions
private func showError(message: String) {
sendButton.isLoading = false
showAlert(title: L("error"), message: message)
}
}