From 161c00c7b955fd40c65625e86a10a49e8425bfdc Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Mon, 13 Oct 2025 16:54:39 +0900 Subject: [PATCH] ... --- src/main/kotlin/core/ScrapingService.kt | 12 ++-- src/main/kotlin/ui/tabs.kt | 85 ++++++++++++++++++------- src/main/kotlin/utils/Global.kt | 2 + 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/core/ScrapingService.kt b/src/main/kotlin/core/ScrapingService.kt index c51ac4a..b2725de 100644 --- a/src/main/kotlin/core/ScrapingService.kt +++ b/src/main/kotlin/core/ScrapingService.kt @@ -49,25 +49,27 @@ object ScrapingService { return results } - // ⭐️ [수정] 함수 시그니처에 selectors 파라미터 추가 suspend fun scrapeArticleByUrl(url: String, selectors: List, logs: MutableList, isBrowserVisible: Boolean, keepSession: Boolean): ScrapedData? { logMessage(logs, Strings.logScrapeUrlStart(url)) val options = ChromeOptions().apply { if (!isBrowserVisible) addArguments("--headless=new"); addArguments("--disable-gpu") } val currentDriver = BrowserManager.getChromeDriver(options) return try { currentDriver.get(url) - Thread.sleep(2000) + Thread.sleep(4000) val doc = Jsoup.parse(currentDriver.pageSource) - // ⭐️ [수정] 인자로 받은 selectors 리스트를 쉼표로 연결하여 Jsoup 쿼리로 사용하고, 첫 번째 요소를 찾습니다. - val articleElement = doc.select(selectors.joinToString(", "))?.firstOrNull() + // ⭐️ [수정] 1. 셀렉터와 일치하는 '모든' 요소를 찾습니다. + val candidateElements = doc.select(selectors.joinToString(", ")) + + // ⭐️ [수정] 2. 찾은 요소들 중에서 텍스트 길이가 100자 이상인 첫 번째 요소를 본문으로 선택합니다. + val articleElement = candidateElements.find { it.text().length >= 100 } if (articleElement == null) { logMessage(logs, Strings.LOG_WARN_ARTICLE_BODY_NOT_FOUND) return null } - // 찾은 요소에서 텍스트와 이미지를 각각 추출합니다. + // 3. 찾은 요소에서 텍스트와 이미지를 각각 추출합니다. val articleContent = articleElement.text() val allImages = articleElement.select("img") .map { it.absUrl("src") } diff --git a/src/main/kotlin/ui/tabs.kt b/src/main/kotlin/ui/tabs.kt index 550e0a4..d31141f 100644 --- a/src/main/kotlin/ui/tabs.kt +++ b/src/main/kotlin/ui/tabs.kt @@ -1,6 +1,7 @@ // ui/tabs/tabs.kt package ui.tabs +import androidx.compose.foundation.HorizontalScrollbar import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -9,6 +10,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.* @@ -93,13 +96,24 @@ fun ScrapBasedPostTab( Text(Strings.TITLE_SELECT_IMAGES, style = MaterialTheme.typography.subtitle1, modifier = Modifier.weight(1f)) Button(onClick = onSaveChanges, enabled = !isLoading && combinedImagesFromSelectedFiles.isNotEmpty()) { Text(Strings.BUTTON_SAVE_IMAGE_SELECTION) } } - LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) { - items(combinedImagesFromSelectedFiles) { imageUrl -> - val isSelected = imageUrl in currentSelectedImages - Box(modifier = Modifier.padding(4.dp)) { - Image(painter = rememberAsyncImagePainter(model = imageUrl, imageLoader = imageLoader), contentDescription = "Scraped Image", modifier = Modifier.size(100.dp).clickable { onImageSelect(imageUrl) }.border(if (isSelected) 4.dp else 0.dp, MaterialTheme.colors.primary), contentScale = ContentScale.Crop) + // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가 + val imageListState = rememberLazyListState() + Box(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) { + LazyRow( + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), // 스크롤바 공간 확보 + state = imageListState + ) { + items(combinedImagesFromSelectedFiles) { imageUrl -> + val isSelected = imageUrl in currentSelectedImages + Box(modifier = Modifier.padding(4.dp)) { + Image(painter = rememberAsyncImagePainter(model = imageUrl, imageLoader = imageLoader), contentDescription = "Scraped Image", modifier = Modifier.size(100.dp).clickable { onImageSelect(imageUrl) }.border(if (isSelected) 4.dp else 0.dp, MaterialTheme.colors.primary), contentScale = ContentScale.Crop) + } } } + HorizontalScrollbar( + modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(), + adapter = rememberScrollbarAdapter(imageListState) + ) } } Spacer(Modifier.height(16.dp)) @@ -163,17 +177,29 @@ fun DirectPostTab( Text(Strings.TITLE_IMAGE_UPLOAD, style = MaterialTheme.typography.h6) Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_IMAGE) } Spacer(Modifier.height(8.dp)) - LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) { - items(uploadedImageFiles) { file -> - Box(modifier = Modifier.padding(4.dp)) { - Image( - painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), - contentDescription = "Uploaded Image", - modifier = Modifier.size(100.dp).clickable { onRemoveUploadedImage(file) }, - contentScale = ContentScale.Crop - ) + + // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가 + val imageListState = rememberLazyListState() + Box(modifier = Modifier.fillMaxWidth().height(120.dp)) { + LazyRow( + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + state = imageListState + ) { + items(uploadedImageFiles) { file -> + Box(modifier = Modifier.padding(4.dp)) { + Image( + painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), + contentDescription = "Uploaded Image", + modifier = Modifier.size(100.dp).clickable { onRemoveUploadedImage(file) }, + contentScale = ContentScale.Crop + ) + } } } + HorizontalScrollbar( + modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(), + adapter = rememberScrollbarAdapter(imageListState) + ) } Spacer(Modifier.height(16.dp)) @@ -216,17 +242,29 @@ fun ReceiptAnalyzerTab( Text(Strings.TITLE_RECEIPT_ANALYZER, style = MaterialTheme.typography.h5, modifier = Modifier.padding(bottom = 8.dp)) Button(onClick = onUploadReceipt, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_RECEIPT) } Spacer(Modifier.height(8.dp)) - LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) { - items(receiptFiles) { file -> - Box(modifier = Modifier.padding(4.dp)) { - Image( - painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), - contentDescription = "Receipt Image", - modifier = Modifier.size(100.dp).clickable { onRemoveReceipt(file) }, - contentScale = ContentScale.Crop - ) + + // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가 + val imageListState = rememberLazyListState() + Box(modifier = Modifier.fillMaxWidth().height(120.dp)) { + LazyRow( + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + state = imageListState + ) { + items(receiptFiles) { file -> + Box(modifier = Modifier.padding(4.dp)) { + Image( + painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), + contentDescription = "Receipt Image", + modifier = Modifier.size(100.dp).clickable { onRemoveReceipt(file) }, + contentScale = ContentScale.Crop + ) + } } } + HorizontalScrollbar( + modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(), + adapter = rememberScrollbarAdapter(imageListState) + ) } Spacer(Modifier.height(16.dp)) OutlinedTextField( @@ -322,7 +360,6 @@ fun ResultTab( } } -// ⭐️ [수정] SettingsTab 함수 시그니처 변경 @Composable fun SettingsTab( generatePromptPrefix: String, diff --git a/src/main/kotlin/utils/Global.kt b/src/main/kotlin/utils/Global.kt index 9c922ed..dbfbd09 100644 --- a/src/main/kotlin/utils/Global.kt +++ b/src/main/kotlin/utils/Global.kt @@ -110,6 +110,8 @@ object Strings { const val DEFAULT_USER_OWN_CONTENT = "예시: 강릉으로 1박 2일 여행을 다녀왔습니다." // ⭐️ [추가] 스크래핑 셀렉터 기본값 val DEFAULT_ARTICLE_SELECTORS = listOf( + "main", + "#post-area", "#postListBody", "#app", ".app",