Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/mz 145 엑셀 파일 다운로드 #80

Merged
merged 4 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions core/ui/src/main/java/com/susu/core/ui/Consts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ val alignList
const val USER_NAME_MAX_LENGTH = 10
val nameRegex = Regex("[a-zA-Z가-힣]{0,10}")

const val INTENT_ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"

enum class SnsProviders(
val path: String,
@StringRes val nameId: Int,
Expand Down
7 changes: 7 additions & 0 deletions data/src/main/java/com/susu/data/data/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.susu.data.data.di

import com.susu.data.data.repository.CategoryConfigRepositoryImpl
import com.susu.data.data.repository.ExcelRepositoryImpl
import com.susu.data.data.repository.LedgerRecentSearchRepositoryImpl
import com.susu.data.data.repository.LedgerRepositoryImpl
import com.susu.data.data.repository.LoginRepositoryImpl
Expand All @@ -9,6 +10,7 @@ import com.susu.data.data.repository.TermRepositoryImpl
import com.susu.data.data.repository.TokenRepositoryImpl
import com.susu.data.data.repository.UserRepositoryImpl
import com.susu.domain.repository.CategoryConfigRepository
import com.susu.domain.repository.ExcelRepository
import com.susu.domain.repository.LedgerRecentSearchRepository
import com.susu.domain.repository.LedgerRepository
import com.susu.domain.repository.LoginRepository
Expand Down Expand Up @@ -64,4 +66,9 @@ abstract class RepositoryModule {
abstract fun bindUserRepository(
userRepositoryImpl: UserRepositoryImpl,
): UserRepository

@Binds
abstract fun bindExcelRepository(
excelRepositoryImpl: ExcelRepositoryImpl,
): ExcelRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.susu.data.data.repository

import android.app.DownloadManager
import android.os.Environment
import androidx.core.net.toUri
import com.susu.domain.repository.ExcelRepository
import com.susu.domain.repository.TokenRepository
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject

class ExcelRepositoryImpl @Inject constructor(
private val downloadManager: DownloadManager,
private val tokenRepository: TokenRepository,
) : ExcelRepository {
override suspend fun downloadEnvelopExcel(): Long {
val token = tokenRepository.getAccessToken().firstOrNull() ?: return -1L
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null인 경우에는 UnknownException을 throw하는게 더 좋을거같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러네요 여기서 -1L을 리턴해버리면 저장된 토큰이 없는 오류와 다운로드 중 발생한 오류인지 구분할 수 없어서 Exception throw 하는게 맞다고 생각이 드네여
반영했습니다~!
d86f0d7


val request = DownloadManager.Request(url.toUri())
.setMimeType(mimeType)
.setAllowedOverMetered(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setTitle(downloaderName)
.addRequestHeader(headerTokenName, token)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)

return downloadManager.enqueue(request)
}

companion object {
private const val url = "https://api.oksusu.site/api/v1/excel/all-envelopes"
private const val mimeType = "application/vnd.ms-excel"
private const val downloaderName = "수수"
private const val headerTokenName = "X-SUSU-AUTH-TOKEN"
private const val fileName = "수수_기록.xlsx"
}
}
21 changes: 21 additions & 0 deletions data/src/main/java/com/susu/data/remote/di/ExcelModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.susu.data.remote.di

import android.app.DownloadManager
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object ExcelModule {

@Provides
@Singleton
fun providesDownloadManager(@ApplicationContext context: Context): DownloadManager {
return context.getSystemService(DownloadManager::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.susu.domain.repository

interface ExcelRepository {
suspend fun downloadEnvelopExcel(): Long
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.susu.domain.usecase.mypage

import com.susu.core.common.runCatchingIgnoreCancelled
import com.susu.domain.repository.ExcelRepository
import javax.inject.Inject

class DownloadExcelUseCase @Inject constructor(
private val excelRepository: ExcelRepository,
) {
suspend operator fun invoke() = runCatchingIgnoreCancelled {
excelRepository.downloadEnvelopExcel()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.susu.feature.mypage.main

import android.os.Environment
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -78,7 +79,7 @@ fun MyPageDefaultRoute(
onShowDialog(
DialogToken(
title = context.getString(com.susu.feature.mypage.R.string.dialog_export_title),
text = context.getString(com.susu.feature.mypage.R.string.dialog_export_detail),
text = context.getString(com.susu.feature.mypage.R.string.dialog_export_detail, Environment.DIRECTORY_DOWNLOADS),
dismissText = context.getString(R.string.word_cancel),
confirmText = context.getString(com.susu.feature.mypage.R.string.dialog_export_confirm),
onConfirmRequest = viewModel::export,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.kakao.sdk.auth.TokenManagerProvider
import com.kakao.sdk.user.UserApiClient
import com.susu.core.model.exception.UserNotFoundException
import com.susu.core.ui.base.BaseViewModel
import com.susu.domain.usecase.mypage.DownloadExcelUseCase
import com.susu.domain.usecase.mypage.GetUserUseCase
import com.susu.domain.usecase.mypage.LogoutUseCase
import com.susu.domain.usecase.mypage.WithdrawUseCase
Expand All @@ -17,6 +18,7 @@ class MyPageViewModel @Inject constructor(
private val logoutUseCase: LogoutUseCase,
private val withdrawUseCase: WithdrawUseCase,
private val getUserUseCase: GetUserUseCase,
private val downloadExcelUseCase: DownloadExcelUseCase,
) : BaseViewModel<MyPageState, MyPageEffect>(MyPageState()) {

init {
Expand Down Expand Up @@ -72,6 +74,10 @@ class MyPageViewModel @Inject constructor(
}

fun export() {
postSideEffect(MyPageEffect.ShowExportSuccessSnackbar)
viewModelScope.launch {
downloadExcelUseCase().onFailure {
postSideEffect(MyPageEffect.HandleException(it, ::export))
}
}
}
}
2 changes: 1 addition & 1 deletion feature/mypage/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<string name="dialog_logout_title">로그아웃 할까요?</string>
<string name="dialog_logout_confirm">로그아웃</string>
<string name="dialog_export_title">엑셀로 내보낼까요?</string>
<string name="dialog_export_detail">모든 기록을 엑셀로 저장해요. 파일은 (경로)에서 확인할 수 있어요.</string>
<string name="dialog_export_detail">모든 기록을 엑셀로 저장해요. 파일은 %s에서 확인할 수 있어요.</string>
<string name="dialog_export_confirm">내보내기</string>
<string name="dialog_withdraw_title">정말 탈퇴하시겠어요?</string>
<string name="dialog_withdraw_detail">계정 정보와 모든 기록이 삭제되며 다시 복구할 수 없어요</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.susu.feature.navigator

import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -11,9 +16,12 @@ import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.susu.core.designsystem.theme.SusuTheme
import com.susu.core.ui.INTENT_ACTION_DOWNLOAD_COMPLETE
import com.susu.core.ui.SnackbarToken
import com.susu.feature.loginsignup.social.KakaoLoginHelper
import dagger.hilt.android.AndroidEntryPoint

Expand All @@ -24,6 +32,8 @@ class MainActivity : ComponentActivity() {
private val uiState
get() = viewModel.uiState.value

private lateinit var broadcastReceiver: BroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
Expand All @@ -45,6 +55,24 @@ class MainActivity : ComponentActivity() {

WindowCompat.setDecorFitsSystemWindows(window, false)

broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == INTENT_ACTION_DOWNLOAD_COMPLETE) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
if (id != -1L) {
viewModel.onShowSnackbar(SnackbarToken(message = context.getString(com.susu.feature.mypage.R.string.snackbar_success_export)))
}
}
}
}

ContextCompat.registerReceiver(
this,
broadcastReceiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
ContextCompat.RECEIVER_EXPORTED,
)
Comment on lines +58 to +74
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyInfoScreen으로 이동 못하나유?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MainActivity의 onDestry()에서 BroadcastReceiver 등록을 해제하기 위해 여기에 위치해있습니당
configuration change가 일어나면 리시버가 해제되지 않은 상태에서 새 리시버가 중복으로 등록될 위험이 있어서요~!


setContent {
SusuTheme {
MainScreen(
Expand All @@ -60,4 +88,9 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onDestroy() {
super.onDestroy()
unregisterReceiver(broadcastReceiver)
}
}