diff --git a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt index 574afff..ecfccc3 100644 --- a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt @@ -40,12 +40,7 @@ class AuthRepositoryImpl @Inject constructor( return@flow } - when(res.code()){ - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(res.code()))) } catch (_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } diff --git a/app/src/main/java/com/isolaatti/common/ErrorMessageViewModel.kt b/app/src/main/java/com/isolaatti/common/ErrorMessageViewModel.kt index 13653c2..a4db4a7 100644 --- a/app/src/main/java/com/isolaatti/common/ErrorMessageViewModel.kt +++ b/app/src/main/java/com/isolaatti/common/ErrorMessageViewModel.kt @@ -3,7 +3,26 @@ package com.isolaatti.common import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.isolaatti.utils.Resource +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow class ErrorMessageViewModel : ViewModel() { - val error: MutableLiveData = MutableLiveData() + val error: MutableLiveData = MutableLiveData() + private val _retry: MutableSharedFlow = MutableSharedFlow() + val retry: SharedFlow get() = _retry.asSharedFlow() + private var handledCount = 0 + + suspend fun askRetry() { + _retry.emit(false) + handledCount = 0 + _retry.emit(true) + } + suspend fun handleRetry() { + val subscribers = _retry.subscriptionCount.value + + if(handledCount >= subscribers) { + _retry.emit(false) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt index cc22d78..4d2dad3 100644 --- a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt +++ b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt @@ -20,6 +20,9 @@ import com.isolaatti.home.HomeActivity import com.isolaatti.login.LogInActivity import com.isolaatti.utils.Resource import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @AndroidEntryPoint abstract class IsolaattiBaseActivity : AppCompatActivity() { @@ -27,9 +30,10 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() { val errorViewModel: ErrorMessageViewModel by viewModels() private var snackbarNetworkStatus: Snackbar? = null + private var snackbarError: Snackbar? = null private var connectionHasBeenLost: Boolean = false - private val errorObserver: Observer = Observer { + private val errorObserver: Observer = Observer { when(it) { Resource.Error.ErrorType.AuthError -> showReAuthDialog() Resource.Error.ErrorType.NetworkError -> showNetworkErrorMessage() @@ -38,6 +42,7 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() { Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage() else -> {} } + errorViewModel.error.postValue(null) } private val connectivityObserver: Observer = Observer { networkAvailable -> @@ -52,21 +57,22 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() { snackbarNetworkStatus = Snackbar.make(view, R.string.network_conn_restored, Snackbar.LENGTH_SHORT) snackbarNetworkStatus?.show() connectionHasBeenLost = false + + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.askRetry() + } + } } private val signInActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult -> if(activityResult.resultCode == Activity.RESULT_OK) { - onRetry() + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.handleRetry() + } } } - /** - * This method is called when a refresh should be performed. For example, - * when sign in flow is started completed from here, it is needed to know - * when it is complete. - */ - abstract fun onRetry() private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ -> signInActivityResult.launch(Intent(this, LogInActivity::class.java)) @@ -78,22 +84,67 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() { .setPositiveButton(R.string.accept, onAcceptReAuthClick) .setNegativeButton(R.string.close, null) .show() + errorViewModel.error.postValue(null) } private fun showNetworkErrorMessage() { - Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show() + val view: View = window.decorView.findViewById(android.R.id.content) ?: return + + + snackbarError = Snackbar.make(view, R.string.network_error, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry) { + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.askRetry() + } + errorViewModel.error.postValue(null) + } + + snackbarError?.show() } private fun showServerErrorMessage() { + val view: View = window.decorView.findViewById(android.R.id.content) ?: return + + snackbarError = Snackbar.make(view, R.string.server_error, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry) { + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.askRetry() + } + errorViewModel.error.postValue(null) + } + + snackbarError?.show() } private fun showNotFoundErrorMessage() { + val view: View = window.decorView.findViewById(android.R.id.content) ?: return + + snackbarError = Snackbar.make(view, R.string.not_found, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry) { + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.askRetry() + } + errorViewModel.error.postValue(null) + } + + snackbarError?.show() } private fun showUnknownErrorMessage() { + val view: View = window.decorView.findViewById(android.R.id.content) ?: return + + snackbarError = Snackbar.make(view, R.string.unknown_error, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry) { + CoroutineScope(Dispatchers.Default).launch { + errorViewModel.askRetry() + } + errorViewModel.error.postValue(null) + } + + snackbarError?.show() } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt b/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt index 7c1ff9a..89a02ed 100644 --- a/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt @@ -2,44 +2,64 @@ package com.isolaatti.followers.data import com.isolaatti.followers.data.remote.FollowersApi import com.isolaatti.followers.domain.FollowersRepository -import com.isolaatti.profile.data.remote.ProfileListItemDto import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.utils.IntIdentificationWrapper +import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import retrofit2.awaitResponse import javax.inject.Inject class FollowersRepositoryImpl @Inject constructor(private val followersApi: FollowersApi) : FollowersRepository { - override fun getFollowersOfUser(userId: Int, after: Int): Flow> = flow { - val response = followersApi.getFollowersOfUser(userId, after).awaitResponse() - if(response.isSuccessful) { - response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) } + override fun getFollowersOfUser(userId: Int, after: Int): Flow>> = flow { + try { + val response = followersApi.getFollowersOfUser(userId, after).awaitResponse() + if(response.isSuccessful) { + response.body()?.let { emit(Resource.Success(response.body()!!.map { ProfileListItem.fromDto(it) })) } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } - override fun getFollowingsOfUser(userId: Int, after: Int): Flow> = flow { - val response = followersApi.getFollowingsOfUser(userId, after).awaitResponse() - if(response.isSuccessful) { - response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) } + override fun getFollowingsOfUser(userId: Int, after: Int): Flow>> = flow { + try { + val response = followersApi.getFollowingsOfUser(userId, after).awaitResponse() + if(response.isSuccessful) { + response.body()?.let { emit(Resource.Success(response.body()!!.map { ProfileListItem.fromDto(it) })) } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } - override fun followUser(userId: Int): Flow = flow { - val response = followersApi.followUser(IntIdentificationWrapper(userId)).awaitResponse() - if(response.isSuccessful) { - emit(true) - } else { - emit(false) + override fun followUser(userId: Int): Flow> = flow { + try { + val response = followersApi.followUser(IntIdentificationWrapper(userId)).awaitResponse() + if(response.isSuccessful) { + emit(Resource.Success(true)) + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } - override fun unfollowUser(userId: Int): Flow = flow { - val response = followersApi.unfollowUser(IntIdentificationWrapper(userId)).awaitResponse() - if(response.isSuccessful) { - emit(true) - } else { - emit(false) + override fun unfollowUser(userId: Int): Flow> = flow { + try { + val response = followersApi.unfollowUser(IntIdentificationWrapper(userId)).awaitResponse() + if(response.isSuccessful) { + emit(Resource.Success(true)) + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt b/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt deleted file mode 100644 index 1f36836..0000000 --- a/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.isolaatti.followers.domain - -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class FollowUserUseCase @Inject constructor(private val followersRepository: FollowersRepository) { - fun follow(userId: Int): Flow { - return followersRepository.followUser(userId) - } - - fun unfollow(userId: Int): Flow { - return followersRepository.unfollowUser(userId) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt b/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt index 6147570..0f5e362 100644 --- a/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt +++ b/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt @@ -1,11 +1,12 @@ package com.isolaatti.followers.domain import com.isolaatti.profile.domain.entity.ProfileListItem +import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow interface FollowersRepository { - fun getFollowersOfUser(userId: Int, after: Int): Flow> - fun getFollowingsOfUser(userId: Int, after: Int): Flow> - fun followUser(userId: Int): Flow - fun unfollowUser(userId: Int): Flow + fun getFollowersOfUser(userId: Int, after: Int): Flow>> + fun getFollowingsOfUser(userId: Int, after: Int): Flow>> + fun followUser(userId: Int): Flow> + fun unfollowUser(userId: Int): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt b/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt index 9fae433..f3bf535 100644 --- a/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt +++ b/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt @@ -4,9 +4,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.isolaatti.followers.domain.FollowUserUseCase import com.isolaatti.followers.domain.FollowersRepository import com.isolaatti.profile.domain.entity.ProfileListItem +import com.isolaatti.utils.Resource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository, private val followUserUseCase: FollowUserUseCase) : ViewModel() { +class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository) : ViewModel() { var userId: Int = 0 private val followersList: MutableList = mutableListOf() @@ -29,6 +29,19 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo val followers: LiveData> get() = _followers val followings: LiveData> get() = _followings + private val toRetry: MutableList = mutableListOf() + + + // runs the lists of "Runnable" one by one and clears list. After this is executed, + // caller should report as handled + fun retry() { + toRetry.forEach { + it.run() + } + + toRetry.clear() + } + private fun getFollowersLastId(): Int { if(followersList.isEmpty()) { @@ -53,11 +66,23 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo viewModelScope.launch { followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach { - followersList.addAll(it) - _followers.postValue(followersList) + when(it) { + is Resource.Success -> { + if(it.data != null) { + followersList.addAll(it.data) + } + + _followers.postValue(followersList) + } + is Resource.Error -> { + toRetry.add { + fetchFollowers() + } + } + is Resource.Loading -> {} + } }.flowOn(Dispatchers.IO).launchIn(this) } - } fun fetchFollowings() { @@ -67,8 +92,20 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo viewModelScope.launch { followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach { - followingsList.addAll(it) - _followings.postValue(followingsList) + when(it) { + is Resource.Error -> { + toRetry.add { + fetchFollowings() + } + } + is Resource.Loading -> {} + is Resource.Success -> { + if(it.data != null) { + followingsList.addAll(it.data) + _followings.postValue(followingsList) + } + } + } }.flowOn(Dispatchers.IO).launchIn(this) } } @@ -96,10 +133,20 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo replaceOnLists(user) viewModelScope.launch { - followUserUseCase.follow(user.id).onEach { - user.following = true - user.updatingFollowState = false - replaceOnLists(user) + followersRepository.followUser(user.id).onEach { + when(it) { + is Resource.Error -> { + toRetry.add { + followUser(user) + } + } + is Resource.Loading -> {} + is Resource.Success -> { + user.following = true + user.updatingFollowState = false + replaceOnLists(user) + } + } }.flowOn(Dispatchers.IO).launchIn(this) } } @@ -110,10 +157,21 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo replaceOnLists(user) viewModelScope.launch { - followUserUseCase.unfollow(user.id).onEach { - user.following = false - user.updatingFollowState = false - replaceOnLists(user) + followersRepository.unfollowUser(user.id).onEach { + when(it) { + is Resource.Error -> { + toRetry.add { + unfollowUser(user) + } + } + is Resource.Loading -> {} + is Resource.Success -> { + user.following = false + user.updatingFollowState = false + replaceOnLists(user) + } + } + }.flowOn(Dispatchers.IO).launchIn(this) } } diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/FeedFragment.kt index afd0d65..0386699 100644 --- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt +++ b/app/src/main/java/com/isolaatti/home/FeedFragment.kt @@ -11,7 +11,10 @@ import android.widget.TextView import android.widget.Toast import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.isolaatti.BuildConfig import com.isolaatti.R @@ -45,6 +48,7 @@ import io.noties.markwon.Markwon import io.noties.markwon.MarkwonConfiguration import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute import io.noties.markwon.linkify.LinkifyPlugin +import kotlinx.coroutines.launch @AndroidEntryPoint class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { @@ -57,7 +61,6 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { private val errorViewModel: ErrorMessageViewModel by activityViewModels() private val viewModel: FeedViewModel by activityViewModels() val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() - private var currentUserId = 0 private lateinit var viewBinding: FragmentFeedBinding @@ -220,9 +223,19 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { viewModel.errorLoading.observe(viewLifecycleOwner) { errorViewModel.error.postValue(it) } + viewModel.getProfile() optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver) + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + errorViewModel.retry.collect { + viewModel.retry() + errorViewModel.handleRetry() + } + } + } } override fun onLiked(postId: Long) = viewModel.likePost(postId) diff --git a/app/src/main/java/com/isolaatti/home/HomeActivity.kt b/app/src/main/java/com/isolaatti/home/HomeActivity.kt index 486e75e..749bdcd 100644 --- a/app/src/main/java/com/isolaatti/home/HomeActivity.kt +++ b/app/src/main/java/com/isolaatti/home/HomeActivity.kt @@ -15,10 +15,6 @@ import com.isolaatti.home.presentation.FeedViewModel class HomeActivity : IsolaattiBaseActivity() { private lateinit var viewBinding: ActivityHomeBinding private val feedViewModel: FeedViewModel by viewModels() - override fun onRetry() { - - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt index c6a938f..b30f4fe 100644 --- a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt +++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt @@ -25,6 +25,19 @@ class FeedViewModel @Inject constructor( private val postsRepository: PostsRepository ) : PostListingViewModelBase() { + private val toRetry: MutableList = mutableListOf() + + + // runs the lists of "Runnable" one by one and clears list. After this is executed, + // caller should report as handled + fun retry() { + toRetry.forEach { + it.run() + } + + toRetry.clear() + } + override fun getFeed(refresh: Boolean) { viewModelScope.launch { if (refresh) { @@ -49,7 +62,10 @@ class FeedViewModel @Inject constructor( } is Resource.Error -> { - //errorLoading.postValue(feedDtoResource.errorType) + errorLoading.postValue(listResource.errorType) + toRetry.add { + getFeed(refresh) + } } } @@ -67,15 +83,24 @@ class FeedViewModel @Inject constructor( authRepository.getUserId().onEach { userId -> userId?.let { getProfileUseCase(userId).onEach { profile -> - if (profile is Resource.Success) { - profile.data?.let { _userProfile.postValue(it) } + + + when(profile) { + is Resource.Error -> { + errorLoading.postValue(profile.errorType) + toRetry.add { + getProfile() + } + } + is Resource.Loading -> {} + is Resource.Success -> { + profile.data?.let { _userProfile.postValue(it) } + } } }.flowOn(Dispatchers.IO).launchIn(this) } }.flowOn(Dispatchers.IO).launchIn(this) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/PostViewerActivity.kt b/app/src/main/java/com/isolaatti/posting/PostViewerActivity.kt index e9cb80b..4e618c2 100644 --- a/app/src/main/java/com/isolaatti/posting/PostViewerActivity.kt +++ b/app/src/main/java/com/isolaatti/posting/PostViewerActivity.kt @@ -21,9 +21,6 @@ class PostViewerActivity : IsolaattiBaseActivity() { private lateinit var binding: ActivityPostViewerBinding private var postId: Long? = null - override fun onRetry() { - TODO("Not yet implemented") - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt index c748d4b..3fb0563 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt @@ -14,47 +14,72 @@ import javax.inject.Inject class CommentsRepositoryImpl @Inject constructor(private val commentsApi: CommentsApi) : CommentsRepository { - override fun getComments(postId: Long, lastId: Long): Flow> = flow { - val response = commentsApi.getCommentsOfPosts(postId, lastId, 15).awaitResponse() - if(response.isSuccessful){ - response.body()?.let { emit(Comment.fromCommentsDto(it).toMutableList()) } + override fun getComments(postId: Long, lastId: Long): Flow>> = flow { + try { + emit(Resource.Loading()) + val response = commentsApi.getCommentsOfPosts(postId, lastId, 15).awaitResponse() + if(response.isSuccessful){ + response.body()?.let { emit(Resource.Success(Comment.fromCommentsDto(it).toMutableList())) } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } - override fun getComment(commentId: Long): Flow = flow { - val response = commentsApi.getComment(commentId).awaitResponse() - if(response.isSuccessful) { - response.body()?.let { emit(it) } + override fun getComment(commentId: Long): Flow> = flow { + try { + emit(Resource.Loading()) + val response = commentsApi.getComment(commentId).awaitResponse() + if(response.isSuccessful) { + response.body()?.let { emit(Resource.Success(it)) } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } override fun postComment(content: String, audioId: String?, postId: Long): Flow> = flow { - emit(Resource.Loading()) - val commentToPostDto = CommentToPostDto(content, audioId) - val response = commentsApi.postComment(postId, commentToPostDto).awaitResponse() - if(response.isSuccessful) { - val responseBody = response.body() - if(responseBody != null) { - emit(Resource.Success(Comment.fromCommentDto(responseBody))) - return@flow + try { + emit(Resource.Loading()) + val commentToPostDto = CommentToPostDto(content, audioId) + val response = commentsApi.postComment(postId, commentToPostDto).awaitResponse() + if(response.isSuccessful) { + val responseBody = response.body() + if(responseBody != null) { + emit(Resource.Success(Comment.fromCommentDto(responseBody))) + return@flow + } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) } + + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } - emit(Resource.Error()) } override fun editComment(commentId: Long, content: String, audioId: String?): Flow> = flow { - emit(Resource.Loading()) - val commentToPostDto = CommentToPostDto(content, audioId) - val response = commentsApi.editComment(commentId, commentToPostDto).awaitResponse() + try { + emit(Resource.Loading()) + val commentToPostDto = CommentToPostDto(content, audioId) + val response = commentsApi.editComment(commentId, commentToPostDto).awaitResponse() - if(response.isSuccessful) { - val responseBody = response.body() - if(responseBody != null) { - emit(Resource.Success(Comment.fromCommentDto(responseBody))) - return@flow + if(response.isSuccessful) { + val responseBody = response.body() + if(responseBody != null) { + emit(Resource.Success(Comment.fromCommentDto(responseBody))) + return@flow + } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) } + } catch(_: Exception) { + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } - emit(Resource.Error()) } diff --git a/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt b/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt index 8f60f0d..137f6df 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt @@ -6,8 +6,8 @@ import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow interface CommentsRepository { - fun getComments(postId: Long, lastId: Long): Flow> - fun getComment(commentId: Long): Flow + fun getComments(postId: Long, lastId: Long): Flow>> + fun getComment(commentId: Long): Flow> fun postComment(content: String, audioId: String?, postId: Long): Flow> fun editComment(commentId: Long, content: String, audioId: String?): Flow> fun deleteComment(commentId: Long): Flow> diff --git a/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt b/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt index 126aa71..3f79552 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt @@ -2,10 +2,11 @@ package com.isolaatti.posting.comments.domain.use_case import com.isolaatti.posting.comments.domain.CommentsRepository import com.isolaatti.posting.comments.domain.model.Comment +import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import javax.inject.Inject class GetComments @Inject constructor(private val commentsRepository: CommentsRepository) { - operator fun invoke(postId: Long, lastId: Long? = null): Flow> = + operator fun invoke(postId: Long, lastId: Long? = null): Flow>> = commentsRepository.getComments(postId, lastId ?: 0) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt index 0728c01..8b56619 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt @@ -34,7 +34,20 @@ class CommentsViewModel @Inject constructor( val noMoreContent: MutableLiveData = MutableLiveData() val commentToEdit: MutableLiveData = MutableLiveData() val finishedEditingComment: MutableLiveData = MutableLiveData() - val error: MutableLiveData = MutableLiveData(false) + val error: MutableLiveData = MutableLiveData() + + private val toRetry: MutableList = mutableListOf() + + + // runs the lists of "Runnable" one by one and clears list. After this is executed, + // caller should report as handled + fun retry() { + toRetry.forEach { + it.run() + } + + toRetry.clear() + } /** * postId to query comments for. First page will be fetched when set. @@ -54,17 +67,30 @@ class CommentsViewModel @Inject constructor( commentsList.clear() } getComments(postId, lastId).onEach { - val eventType = - if ((commentsList.isNotEmpty())) UpdateEvent.UpdateType.COMMENT_PAGE_ADDED_BOTTOM else UpdateEvent.UpdateType.REFRESH - commentsList.addAll(it) - _comments.postValue(Pair(commentsList, UpdateEvent(eventType, null))) - if (it.isEmpty()) { - noMoreContent.postValue(true) + when(it) { + is Resource.Error -> { + error.postValue(it.errorType) + toRetry.add { + getContent(refresh) + } + } + is Resource.Loading -> {} + is Resource.Success -> { + val eventType = + if ((commentsList.isNotEmpty())) UpdateEvent.UpdateType.COMMENT_PAGE_ADDED_BOTTOM else UpdateEvent.UpdateType.REFRESH + if(it.data == null) { + return@onEach + } + commentsList.addAll(it.data) + _comments.postValue(Pair(commentsList, UpdateEvent(eventType, null))) + if (it.data.isEmpty()) { + noMoreContent.postValue(true) + } + if (it.data.isNotEmpty()) { + lastId = it.data.last().id + } + } } - if (it.isNotEmpty()) { - lastId = it.last().id - } - }.flowOn(Dispatchers.IO).launchIn(this) } } @@ -84,7 +110,13 @@ class CommentsViewModel @Inject constructor( is Resource.Error -> { commentPosted.postValue(false) - error.postValue(true) + error.postValue(it.errorType) + + + // this is the original call, put to retry + toRetry.add { + postComment(content) + } } } }.flowOn(Dispatchers.IO).launchIn(this) @@ -111,7 +143,11 @@ class CommentsViewModel @Inject constructor( } is Resource.Error -> { - error.postValue(true) + error.postValue(commentResource.errorType) + + toRetry.add { + editComment(newContent) + } } is Resource.Loading -> {} @@ -129,7 +165,11 @@ class CommentsViewModel @Inject constructor( deleteCommentUseCase(commentId).onEach { when(it) { is Resource.Error -> { - error.postValue(true) + error.postValue(it.errorType) + + toRetry.add { + deleteComment(commentId) + } } is Resource.Loading -> {} is Resource.Success -> { diff --git a/app/src/main/java/com/isolaatti/posting/comments/ui/BottomSheetPostComments.kt b/app/src/main/java/com/isolaatti/posting/comments/ui/BottomSheetPostComments.kt index 8a1d78d..5ea39a7 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/ui/BottomSheetPostComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/ui/BottomSheetPostComments.kt @@ -10,7 +10,10 @@ import androidx.core.view.doOnLayout import androidx.core.widget.doOnTextChanged import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED @@ -19,6 +22,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.isolaatti.R import com.isolaatti.common.Dialogs +import com.isolaatti.common.ErrorMessageViewModel import com.isolaatti.databinding.BottomSheetPostCommentsBinding import com.isolaatti.posting.comments.domain.model.Comment import com.isolaatti.posting.comments.presentation.CommentsRecyclerViewAdapter @@ -38,6 +42,8 @@ import io.noties.markwon.Markwon import io.noties.markwon.MarkwonConfiguration import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute import io.noties.markwon.linkify.LinkifyPlugin +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch @AndroidEntryPoint class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedCallback { @@ -45,8 +51,8 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC private lateinit var viewBinding: BottomSheetPostCommentsBinding val viewModel: CommentsViewModel by viewModels() private lateinit var adapter: CommentsRecyclerViewAdapter - - val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() + private val errorViewModel: ErrorMessageViewModel by activityViewModels() + private val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() private val optionsObserver: Observer = Observer { optionClicked -> if(optionClicked?.callerId == CALLER_ID) { @@ -117,10 +123,7 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC viewModel.commentPosted.observe(viewLifecycleOwner, commentPostedObserver) viewModel.commentToEdit.observe(viewLifecycleOwner, commentToEditObserver) viewModel.error.observe(viewLifecycleOwner) { - if(it == true) { - Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show() - viewModel.error.postValue(null) - } + errorViewModel.error.postValue(it) } optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver) @@ -200,6 +203,18 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC // even if no change event is triggered viewBinding.submitCommentButton.isEnabled = !viewBinding.newCommentTextField.editText?.text.isNullOrBlank() + + + // things to retry when user taps "Retry" + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + errorViewModel.retry.collect { + viewModel.retry() + errorViewModel.handleRetry() + } + } + } + setObservers() setListeners() } diff --git a/app/src/main/java/com/isolaatti/posting/comments/ui/EditCommentDialogFragment.kt b/app/src/main/java/com/isolaatti/posting/comments/ui/EditCommentDialogFragment.kt index 601ba85..10bebbe 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/ui/EditCommentDialogFragment.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/ui/EditCommentDialogFragment.kt @@ -83,10 +83,7 @@ class EditCommentDialogFragment : DialogFragment() { } viewModel.error.observe(viewLifecycleOwner) { - if(it) { - Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show() - viewModel.error.postValue(true) - } + viewModel.error.postValue(it) } viewModel.finishedEditingComment.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt index 24543c5..a20d417 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt @@ -14,6 +14,7 @@ import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import retrofit2.awaitResponse import javax.inject.Inject class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository { @@ -40,17 +41,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr emit(Resource.Loading()) try { val gson = Gson() - val result = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).execute() - if(result.isSuccessful) { - emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) })) + val response = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).awaitResponse() + if(response.isSuccessful) { + emit(Resource.Success(response.body()?.let { Post.fromFeedDto(it) })) return@flow } - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } @@ -59,17 +55,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr override fun makePost(createPostDto: CreatePostDto): Flow> = flow { emit(Resource.Loading()) try { - val result = postApi.makePost(createPostDto).execute() + val result = postApi.makePost(createPostDto).awaitResponse() if(result.isSuccessful) { emit(Resource.Success(result.body())) return@flow } - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } @@ -78,17 +69,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr override fun editPost(editPostDto: EditPostDto): Flow> = flow { emit(Resource.Loading()) try { - val result = postApi.editPost(editPostDto).execute() + val result = postApi.editPost(editPostDto).awaitResponse() if(result.isSuccessful) { emit(Resource.Success(result.body())) return@flow } - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } @@ -102,12 +88,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr emit(Resource.Success(result.body())) return@flow } - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } @@ -121,12 +102,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr emit(Resource.Success(result.body())) return@flow } - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } diff --git a/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt b/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt index 6c313ba..467ba15 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt @@ -6,11 +6,15 @@ import android.os.Bundle import android.view.View import androidx.activity.viewModels import androidx.core.widget.doOnTextChanged +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.isolaatti.R import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.databinding.ActivityCreatePostBinding import com.isolaatti.posting.posts.presentation.CreatePostViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint class CreatePostActivity : IsolaattiBaseActivity() { @@ -34,13 +38,6 @@ class CreatePostActivity : IsolaattiBaseActivity() { val viewModel: CreatePostViewModel by viewModels() var mode: Int = EXTRA_MODE_CREATE var postId: Long = 0L - override fun onRetry() { - if(mode == EXTRA_MODE_EDIT && postId != 0L) { - viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString()) - } else { - viewModel.postDiscussion(binding.filledTextField.editText?.text.toString()) - } - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,9 +59,24 @@ class CreatePostActivity : IsolaattiBaseActivity() { setupUI() setListeners() setObservers() - setContentView(binding.root) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + errorViewModel.retry.collect { + if(!it) { + return@collect + } + + if(mode == EXTRA_MODE_EDIT && postId != 0L) { + viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString()) + } else { + viewModel.postDiscussion(binding.filledTextField.editText?.text.toString()) + } + } + } + } + } private fun setupUI() { diff --git a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt index 5af4f67..5d13e3d 100644 --- a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt @@ -6,24 +6,18 @@ import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import retrofit2.await +import retrofit2.awaitResponse import javax.inject.Inject class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository { override fun getProfile(userId: Int): Flow> = flow { try { - val result = profileApi.userProfile(userId).execute() + val result = profileApi.userProfile(userId).awaitResponse() if(result.isSuccessful) { emit(Resource.Success(result.body())) return@flow } - - when(result.code()) { - 401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError)) - 404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError)) - 500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError)) - else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError)) - } + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) } catch(_: Exception) { emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } diff --git a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt index 907ed31..5c246f1 100644 --- a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt +++ b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt @@ -35,21 +35,43 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro val followingState: MutableLiveData = MutableLiveData() + private val toRetry: MutableList = mutableListOf() + + + // runs the lists of "Runnable" one by one and clears list. After this is executed, + // caller should report as handled + fun retry() { + toRetry.forEach { + it.run() + } + + toRetry.clear() + } + fun getProfile() { viewModelScope.launch { getProfileUseCase(profileId).onEach { - if(it is Resource.Success) { - _profile.postValue(it.data!!) - followingState.postValue( - it.data.let {user-> - when { - user.followingThisUser && user.thisUserIsFollowingMe -> FollowingState.MutuallyFollowing - user.followingThisUser -> FollowingState.FollowingThisUser - user.thisUserIsFollowingMe -> FollowingState.ThisUserIsFollowingMe - else -> FollowingState.NotMutuallyFollowing - } + when(it) { + is Resource.Error -> { + errorLoading.postValue(it.errorType) + toRetry.add { + getProfile() } - ) + } + is Resource.Loading -> {} + is Resource.Success -> { + _profile.postValue(it.data!!) + followingState.postValue( + it.data.let {user-> + when { + user.followingThisUser && user.thisUserIsFollowingMe -> FollowingState.MutuallyFollowing + user.followingThisUser -> FollowingState.FollowingThisUser + user.thisUserIsFollowingMe -> FollowingState.ThisUserIsFollowingMe + else -> FollowingState.NotMutuallyFollowing + } + } + ) + } } }.flowOn(Dispatchers.IO).launchIn(this) } @@ -75,6 +97,9 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro is Resource.Error -> { errorLoading.postValue(feedDtoResource.errorType) + toRetry.add { + getFeed(refresh) + } } } }.flowOn(Dispatchers.IO).launchIn(this) diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt index 7979fdd..c51761d 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt @@ -24,6 +24,7 @@ import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener import com.google.android.material.tabs.TabLayoutMediator import com.isolaatti.BuildConfig import com.isolaatti.R +import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.databinding.ActivityProfileBinding import com.isolaatti.posting.common.domain.OnUserInteractedCallback import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback @@ -42,7 +43,7 @@ import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAb import io.noties.markwon.linkify.LinkifyPlugin @AndroidEntryPoint -class ProfileActivity : FragmentActivity() { +class ProfileActivity : IsolaattiBaseActivity() { lateinit var viewBinding: ActivityProfileBinding diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt index 889c515..094ff53 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt @@ -9,7 +9,10 @@ import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.isolaatti.BuildConfig @@ -43,6 +46,7 @@ import io.noties.markwon.Markwon import io.noties.markwon.MarkwonConfiguration import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute import io.noties.markwon.linkify.LinkifyPlugin +import kotlinx.coroutines.launch @AndroidEntryPoint class ProfileMainFragment : Fragment() { @@ -306,5 +310,16 @@ class ProfileMainFragment : Fragment() { bind() setObservers() getData() + + + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + errorViewModel.retry.collect { + viewModel.retry() + errorViewModel.handleRetry() + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/utils/Resource.kt b/app/src/main/java/com/isolaatti/utils/Resource.kt index 06c2cbf..87523fe 100644 --- a/app/src/main/java/com/isolaatti/utils/Resource.kt +++ b/app/src/main/java/com/isolaatti/utils/Resource.kt @@ -7,5 +7,15 @@ sealed class Resource { enum class ErrorType { NetworkError, AuthError, NotFoundError, ServerError, OtherError } + companion object { + fun mapErrorCode(errorCode: Int): ErrorType { + return when(errorCode) { + 401 -> ErrorType.AuthError + 404 -> ErrorType.NotFoundError + 505 -> ErrorType.ServerError + else -> ErrorType.OtherError + } + } + } } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c033ef..be034ce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,4 +77,7 @@ Edit comment Network connection lost Network connection restored + Retry + The resource you are trying to load could not be found + An unkwnow error occurred \ No newline at end of file