...
This commit is contained in:
parent
378f6495be
commit
0400385cf8
@ -181,7 +181,6 @@ class CompletedFilesFragment : Fragment() {
|
||||
loadFiles()
|
||||
} else {
|
||||
val intentFlags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
if (extVideos.contains(file.extension.lowercase())) {
|
||||
trackFileAccess(file.name)
|
||||
loadFiles()
|
||||
@ -192,6 +191,8 @@ class CompletedFilesFragment : Fragment() {
|
||||
}
|
||||
startActivity(intent)
|
||||
} else if (extImages.contains(file.extension.lowercase())) {
|
||||
trackFileAccess(file.name)
|
||||
loadFiles()
|
||||
val intent = Intent(requireContext(), ImageViewerActivity::class.java).apply {
|
||||
putExtra("IMAGE_PATH", file.absolutePath)
|
||||
// 💡 이미지 뷰어도 동일하게 적용
|
||||
@ -199,6 +200,8 @@ class CompletedFilesFragment : Fragment() {
|
||||
}
|
||||
startActivity(intent)
|
||||
} else if (extDocs.contains(file.extension.lowercase())) {
|
||||
trackFileAccess(file.name)
|
||||
loadFiles()
|
||||
val intent = Intent(requireContext(), DocumentViewerActivity::class.java).apply {
|
||||
putExtra("FILE_PATH", file.absolutePath)
|
||||
// 💡 문서 뷰어도 동일하게 적용
|
||||
|
||||
@ -633,7 +633,7 @@ open class NeoRssActivity : CommonActivity() {
|
||||
// R.id.webtoons -> TokiFragment.newInstanceWebtoons()
|
||||
// R.id.comics -> TokiFragment.newInstanceComics()
|
||||
R.id.youtube -> TokiFragment.newInstanceYouTube()
|
||||
R.id.perplexity -> TokiFragment.newInstancePerplexity()
|
||||
// R.id.perplexity -> TokiFragment.newInstancePerplexity()
|
||||
R.id.zzalbang -> BookmarkPagerFragment()
|
||||
R.id.btn_x -> TokiFragment.newInstanceX()
|
||||
R.id.btn_i -> TokiFragment.newInstanceI()
|
||||
|
||||
@ -64,32 +64,24 @@ class PagedTextLayout : ConstraintLayout , PagedTextGenerateInterface {
|
||||
var guideLine : Guideline? = null
|
||||
var pageList = mutableListOf<CharSequence>()
|
||||
var summaryText : String = ""
|
||||
var text : String = ""
|
||||
set(new) {
|
||||
field = new
|
||||
val summary = new.replace(" " ,"").replace("\n" ,"").substring(0,Math.min(30,new.length))
|
||||
if (summary.equals(summaryText) && summaryText.length > 100) {
|
||||
var text: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
if (value.isNotEmpty()) {
|
||||
// 액티비티가 준 한 페이지 분량의 텍스트를 곧바로 자식 텍스트뷰에 바인딩합니다.
|
||||
mainTextView?.text = value
|
||||
|
||||
} else {
|
||||
if (field.length > 0) {
|
||||
post {
|
||||
if (width > 0 && height > 0) {
|
||||
MainScope().launch {
|
||||
pageList.clear()
|
||||
val contentWidth =
|
||||
mainTextView!!.width - (mainTextView!!.paddingLeft + mainTextView!!.paddingRight)
|
||||
val contentHeight =
|
||||
mainTextView!!.height - (mainTextView!!.paddingTop + mainTextView!!.paddingBottom)
|
||||
val pages = paginateAsync(cleanText(text), contentWidth, contentHeight)
|
||||
pageList.addAll(pages.map { it.trim() })
|
||||
Blog.LOGE("pages >>> ${pages.size}")
|
||||
setPageBy(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 만약 듀얼 페이지 모드를 지원한다면 액티비티에서 2페이지 분량을
|
||||
// 각각 나눠서 주입하는 것이 좋으므로, 여기서는 단순 텍스트 셋팅만 수행합니다.
|
||||
if (isDualPage()) {
|
||||
// 필요 시 듀얼 페이지 처리 로직을 액티비티 스펙에 맞게 확장할 수 있습니다.
|
||||
// 현재는 단일 페이지 기반으로 작동하므로 비워두거나 서브 뷰를 비웁니다.
|
||||
sencondTextView?.text = ""
|
||||
}
|
||||
} else {
|
||||
mainTextView?.text = ""
|
||||
sencondTextView?.text = ""
|
||||
}
|
||||
summaryText = summary
|
||||
}
|
||||
|
||||
private fun cleanText(text: String): String {
|
||||
|
||||
@ -6,6 +6,7 @@ import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@ -107,14 +108,15 @@ class DocumentViewerActivity : AppCompatActivity(), PagedTextViewInterface {
|
||||
}
|
||||
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
// 볼륨 업 키 -> 이전 페이지로 이동 (기존 온스wipeRight 로직 활용)
|
||||
if (currentPageIndex > 0) {
|
||||
showPage(currentPageIndex - 1)
|
||||
}
|
||||
true // 시스템 볼륨 UI가 뜨지 않도록 이벤트를 차단(소비)합니다.
|
||||
}
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
// 볼륨 다운 키 -> 다음 페이지로 이동 (기존 온스wipeLeft 로직 활용)
|
||||
if (currentPageIndex < pageIndexer.pageOffsets.size - 1) {
|
||||
showPage(currentPageIndex + 1)
|
||||
@ -174,22 +176,45 @@ class DocumentViewerActivity : AppCompatActivity(), PagedTextViewInterface {
|
||||
|
||||
private fun showChapterListDialog() {
|
||||
if (!::pageIndexer.isInitialized || pageIndexer.chapters.isEmpty()) {
|
||||
// 챕터가 아직 파싱되지 않았거나 없는 경우 예외 처리
|
||||
Toast.makeText(this, "인덱싱된 챕터가 없습니다.", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val chapterTitles = pageIndexer.chapters.map { "${it.title} (p.${it.pageIndex + 1})" }.toTypedArray()
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("목차 (Chapters)")
|
||||
.setItems(chapterTitles) { dialog, which ->
|
||||
// 사용자가 선택한 챕터의 pageIndex로 바로 이동
|
||||
val targetChapter = pageIndexer.chapters[which]
|
||||
showPage(targetChapter.pageIndex)
|
||||
dialog.dismiss()
|
||||
// 1. 현재 읽고 있는 페이지가 어떤 챕터에 속해 있는지 index를 찾습니다.
|
||||
var currentChapterIndex = 0
|
||||
for (i in pageIndexer.chapters.indices) {
|
||||
if (pageIndexer.chapters[i].pageIndex <= currentPageIndex) {
|
||||
currentChapterIndex = i
|
||||
} else {
|
||||
break
|
||||
}
|
||||
.setNegativeButton("닫기", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
// 2. 챕터 제목들을 리스트뷰에 보여주기 위한 배열 변환
|
||||
val chapterTitles = pageIndexer.chapters.map { it.title }.toTypedArray()
|
||||
|
||||
// 3. AlertDialog 생성
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle("목차 (현재 위치로 이동)")
|
||||
|
||||
// setItems 대신 Adapter를 직접 지정하거나 리스트를 생성해야 스크롤 제어가 가능합니다.
|
||||
builder.setItems(chapterTitles) { dialog, which ->
|
||||
// 선택한 챕터의 페이지로 이동
|
||||
val targetPage = pageIndexer.chapters[which].pageIndex
|
||||
showPage(targetPage)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
|
||||
// 4. [핵심] 다이얼로그가 화면에 나타난 후, 내부 ListView를 찾아 현재 읽던 챕터 위치로 스크롤을 올립니다.
|
||||
dialog.listView?.let { listView ->
|
||||
listView.post {
|
||||
// 현재 읽고 있는 챕터(currentChapterIndex)가 리스트의 최상단에 보이도록 스크롤 이동
|
||||
listView.setSelection(currentChapterIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -11,10 +11,9 @@ import java.io.RandomAccessFile
|
||||
import java.nio.charset.Charset
|
||||
import java.util.regex.Pattern
|
||||
|
||||
// 1. 챕터 정보를 저장할 데이터 클래스 정의
|
||||
data class Chapter(
|
||||
val title: String, // 챕터 이름 (예: "제 1화 시작하며")
|
||||
val pageIndex: Int // 해당 챕터가 시작되는 페이지 인덱스
|
||||
val title: String,
|
||||
val pageIndex: Int
|
||||
)
|
||||
|
||||
class PageIndexer(
|
||||
@ -25,14 +24,9 @@ class PageIndexer(
|
||||
private val viewHeight: Int
|
||||
) {
|
||||
val pageOffsets = ArrayList<Long>()
|
||||
|
||||
// 2. 추출된 챕터들을 담을 리스트 생성
|
||||
val chapters = ArrayList<Chapter>()
|
||||
|
||||
private val charset = Charset.forName(encoding)
|
||||
|
||||
// 3. 탐지할 챕터 패턴 정규식 정의 (예: "제 1화", "제1장", "Chapter 5", "CH.3" 등 대응)
|
||||
// 소설이나 텍스트 특성에 맞게 패턴을 수정하시면 됩니다.
|
||||
private val chapterPattern = Pattern.compile(
|
||||
"(?:제\\s*)?\\d+\\s*[화|장|막|절|편]"
|
||||
)
|
||||
@ -80,37 +74,58 @@ class PageIndexer(
|
||||
if (endLine < startLine) endLine = startLine
|
||||
|
||||
val endCharOffset = layout.getLineEnd(endLine)
|
||||
var pageText = chunkText.substring(chunkConsumedOffset, endCharOffset)
|
||||
|
||||
val pageText = chunkText.substring(chunkConsumedOffset, endCharOffset)
|
||||
|
||||
// 4. [핵심] 현재 잘라낸 페이지 텍스트 내에 챕터 패턴이 존재하는지 검사
|
||||
// 페이지의 첫 부분 위주로 검사하거나 문단 단위로 첫 줄을 검사하는 것이 정확합니다.
|
||||
// -------------------------------------------------------------------------
|
||||
// [수정 구간] 챕터 탐지 및 강제 페이지 분할 로직
|
||||
// -------------------------------------------------------------------------
|
||||
val matcher = chapterPattern.matcher(pageText)
|
||||
if (matcher.find()) {
|
||||
// 해당 페이지 내에서 실제 챕터 제목으로 쓸 만한 한 줄(Line) 전체를 가져옵니다.
|
||||
// 보통 챕터 제목은 한 줄을 통째로 차지하므로, 패턴이 발견된 위치의 앞뒤 줄바꿈(\n)을 기준으로 잘라냅니다.
|
||||
val matchStart = matcher.start()
|
||||
|
||||
// 패턴 시작점 기준 앞쪽 줄바꿈 찾기
|
||||
// 챕터 제목 줄의 시작 위치 계산
|
||||
val lineStart = pageText.lastIndexOf('\n', matchStart).let { if (it == -1) 0 else it + 1 }
|
||||
// 패턴 시작점 기준 뒤쪽 줄바꿈 찾기
|
||||
val lineEnd = pageText.indexOf('\n', matchStart).let { if (it == -1) pageText.length else it }
|
||||
val chapterTitle = pageText.substring(lineStart, lineEnd).trim()
|
||||
|
||||
var chapterTitle = pageText.substring(lineStart, lineEnd).trim()
|
||||
|
||||
// 제목이 너무 길면 본문 문장이 오탐지된 것일 수 있으므로 글자수 제한(예: 40자)을 둡니다.
|
||||
// 글자수 제한 체크 (40자 미만 정상 챕터인 경우)
|
||||
if (chapterTitle.isNotEmpty() && chapterTitle.length < 40) {
|
||||
val currentPageIndex = pageOffsets.size - 1
|
||||
|
||||
// 중복 등록 방지 (동일 페이지 내 다중 감지 방어)
|
||||
if (chapters.isEmpty() || chapters.last().pageIndex != currentPageIndex) {
|
||||
chapters.add(Chapter(chapterTitle, currentPageIndex))
|
||||
// 만약 챕터가 페이지의 맨 처음(lineStart == 0)이 아니라면,
|
||||
// 즉, 챕터 앞에 이전 장의 본문 내용이 포함되어 있다면 강제로 쪼갭니다.
|
||||
if (lineStart > 0) {
|
||||
// 1. 챕터 직전 내용까지만 현재 페이지 텍스트로 인정합니다.
|
||||
val previousSectionText = pageText.substring(0, lineStart)
|
||||
val previousSectionBytes = previousSectionText.toByteArray(charset).size
|
||||
|
||||
// 2. 현재 오프셋에 이전 내용 크기를 더해 "새 페이지(챕터 시작점)" 오프셋을 구합니다.
|
||||
currentOffset += previousSectionBytes
|
||||
|
||||
// 3. 페이지 목록에 새 오프셋(챕터의 시작점)을 즉시 추가합니다.
|
||||
pageOffsets.add(currentOffset)
|
||||
|
||||
// 4. 다음 루프를 위해 chunk 내 소비된 문자 인덱스를 업데이트합니다.
|
||||
chunkConsumedOffset += lineStart
|
||||
|
||||
// 5. StaticLayout 상에서 이 챕터 줄이 속한 실제 Line을 찾아서 루프의 startLine을 갱신합니다.
|
||||
// layout.getLineForOffset은 전체 chunkText 기준이므로 chunkConsumedOffset을 대입합니다.
|
||||
startLine = layout.getLineForOffset(chunkConsumedOffset)
|
||||
|
||||
// 중요: 이번 루프는 여기서 끊고, 쪼개진 새 페이지 기준으로 다음 루프에서 처리하도록 넘깁니다.
|
||||
continue
|
||||
} else {
|
||||
// 이미 페이지의 맨 첫 줄이 챕터인 경우 바로 리스트에 등록합니다.
|
||||
val currentPageIndex = pageOffsets.size - 1
|
||||
if (chapters.isEmpty() || chapters.last().pageIndex != currentPageIndex) {
|
||||
chapters.add(Chapter(chapterTitle, currentPageIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// 챕터 분할이 일어나지 않은 일반적인 페이지 처리 flow
|
||||
val pageBytesSize = pageText.toByteArray(charset).size
|
||||
|
||||
currentOffset += pageBytesSize
|
||||
chunkConsumedOffset = endCharOffset
|
||||
|
||||
|
||||
@ -210,7 +210,7 @@ var batteryPct = 50
|
||||
|
||||
|
||||
// 2. 파일 다운로드: 계산된 점수(finalScore)가 낮은 순으로 정렬
|
||||
if (isCharging && batteryPct > 70) {
|
||||
if (isCharging && batteryPct > 75) {
|
||||
torrentsWithoutMetadata.forEach { it.swig().resume() }
|
||||
val maxSlots = if (isWifiConnected) 6 else 1
|
||||
val sortedByPriority = torrentsWithMetadata.sortedBy { it.second }
|
||||
|
||||
@ -58,10 +58,10 @@
|
||||
app:fab_label="📺"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/youtube"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="🤖"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/perplexity"/>
|
||||
<!-- <bums.lunatic.launcher.view.FloatingActionButton-->
|
||||
<!-- app:fab_label="🤖"-->
|
||||
<!-- style="@style/CommonFabStyle"-->
|
||||
<!-- android:id="@+id/perplexity"/>-->
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="😂"
|
||||
android:visibility="gone"
|
||||
@ -93,7 +93,6 @@
|
||||
/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="📦"
|
||||
android:visibility="gone"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/btn_completed_files"
|
||||
/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user