Compare commits

..
16 Commits
Author SHA1 Message Date
lun_admin 7fe2cd5674 . 2024-08-25 14:46:14 +09:00
lun_admin 06e95de43d ... 2024-08-22 20:38:56 +09:00
lunaticbumandlunaticbum 0232c9dad0 . 2024-08-12 17:43:17 +09:00
lunaticbumandlunaticbum 536e917b29 /// 2024-08-07 16:18:36 +09:00
lunaticbumandlunaticbum 55a0b209ec ... 2024-08-05 18:03:14 +09:00
lunaticbumandlunaticbum c249e79e9a ... 2024-08-02 16:30:05 +09:00
lunaticbumandlunaticbum f107c7c84d 다시 토끼로... 2024-08-02 13:20:22 +09:00
lunaticbumandlunaticbum ceb22bcbcb //// 2024-08-01 18:10:08 +09:00
lunaticbumandlunaticbum aa4fec37cf ... 2024-08-01 18:04:10 +09:00
lun_admin 4c620872af .... 2024-07-31 20:38:44 +09:00
lun_admin ae743bf784 . 2023-04-06 19:18:02 +09:00
lun_admin 271e0c66dc ... 2023-03-30 18:50:33 +09:00
lun_admin 63ebcab28d . 2023-03-30 17:29:29 +09:00
lun_admin 06e62b4a17 설정 기능 추가중 2023-03-30 17:29:03 +09:00
lun_admin 2d00a605e0 업뎃 2023-03-27 18:43:46 +09:00
lun_admin 389182c38a 일단 커밋 2023-03-26 20:40:07 +09:00
93 changed files with 5783 additions and 49 deletions
+14 -49
View File
@@ -1,50 +1,15 @@
# These are some examples of commonly ignored file patterns. *.iml
# You should customize this list as applicable to your project. .gradle
# Learn more about .gitignore: /local.properties
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore /.idea/caches
/.idea/libraries
# Node artifact files /.idea/modules.xml
node_modules/ /.idea/workspace.xml
dist/ /.idea/navEditor.xml
/.idea/assetWizardSettings.xml
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store .DS_Store
/build
# Generated by Windows /captures
Thumbs.db .externalNativeBuild
.cxx
# Applications local.properties
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
+11
View File
@@ -0,0 +1,11 @@
# tokkiz
resource from {
https://www.flaticon.com/search/2?word=rotate&color=black&shape=fill
}
한글 폰트 from {
https://gongu.copyright.or.kr/gongu/bbs/B0000018/list.do?menuNo=200195
https://noonnu.cc/font_page/1075
}
+1
View File
@@ -0,0 +1 @@
/build
Binary file not shown.
+74
View File
@@ -0,0 +1,74 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'io.realm.kotlin'
// id 'kotlin-android-extensions'
}
android {
namespace 'com.mime.dualscreenview'
compileSdk 34
defaultConfig {
applicationId "com.mime.dualscreenview"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
debug {
storeFile = file("./bs_debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'resources.arsc'
exclude 'AndroidManifest.xml'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.collection:collection-ktx:1.4.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
// implementation files('libs/DualScreen.jar')
implementation 'io.realm.kotlin:library-base:2.0.0'
// testImplementation 'junit:junit:4.13.2'
}
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package com.mime.dualscreenview
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.mime.dualscreenview", appContext.packageName)
}
}
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DualScreenView"
tools:targetApi="31" >
<activity
android:name=".activity.Intro"
android:launchMode="singleTask"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:launchMode="singleInstance"
android:name=".activity.Main"
android:exported="false">
</activity>
<activity
android:launchMode="singleInstance"
android:name=".activity.Settings"
android:exported="false"
/>
</application>
</manifest>
@@ -0,0 +1,11 @@
package com.mime.dualscreenview
import android.app.Application
import io.realm.kotlin.Realm
class BaseAppication : Application() {
override fun onCreate() {
super.onCreate()
}
}
@@ -0,0 +1,131 @@
package com.mime.dualscreenview.activity
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.text.SpannableStringBuilder
import android.text.style.RelativeSizeSpan
import android.view.Gravity
import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
import android.view.KeyEvent.KEYCODE_MEDIA_NEXT
import android.view.KeyEvent.KEYCODE_MEDIA_PREVIOUS
import android.view.KeyEvent.KEYCODE_MEDIA_REWIND
import android.view.KeyEvent.KEYCODE_VOLUME_DOWN
import android.view.KeyEvent.KEYCODE_VOLUME_MUTE
import android.view.KeyEvent.KEYCODE_VOLUME_UP
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.mime.dualscreenview.common.Blog
import com.mime.dualscreenview.data.HistoryManager
import com.mime.dualscreenview.data.model.HistoryItem
import com.mime.dualscreenview.data.model.LastInfo
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.types.BaseRealmObject
import kotlin.reflect.KClass
open class Base : AppCompatActivity() {
inner class BaseHandler(looper : Looper = Looper.getMainLooper()) : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
val baseHandler = BaseHandler()
var didBackPress = false
val onBackPressRunnable = Runnable {
didBackPress = false
}
val backPressString = arrayOf(
"이퓨 워너 백투웹?\n플리즈~\n한번더 뒤로가기!!",
"이퓨 워너 종료?\n플리즈~\n한번더 뒤로가기!~!",
)
fun firstBackPress() {
didBackPress = true
val origin = "이퓨 워너 종료?\n플리즈~ 한번더 뒤로가기!~!"
val biggerText = SpannableStringBuilder(origin)
biggerText.setSpan(RelativeSizeSpan(1.6f), 0, origin.length, 0)
Toast.makeText(baseContext,biggerText,Toast.LENGTH_LONG).apply {
setGravity(Gravity.CENTER, 0, 0)
}.show()
baseHandler.postDelayed(onBackPressRunnable,1500L)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Blog.LOGD(log = "keyCode : ${keyCode}, event : ${event}")
return when(keyCode) {
KEYCODE_VOLUME_DOWN -> { true }
KEYCODE_VOLUME_UP -> { true }
KEYCODE_VOLUME_MUTE -> { true }
KEYCODE_MEDIA_FAST_FORWARD -> { true }
KEYCODE_MEDIA_REWIND -> { true }
KEYCODE_MEDIA_PREVIOUS -> { true }
KEYCODE_MEDIA_NEXT -> { true }
KEYCODE_BACK -> { false }
else -> {
super.onKeyDown(keyCode, event)
}
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
Blog.LOGD(log = "keyCode : ${keyCode}, event : ${event}")
return when(keyCode) {
KEYCODE_VOLUME_DOWN -> {
onKeyClick(keyCode)
true
}
KEYCODE_VOLUME_UP -> {
onKeyClick(keyCode)
true
}
KEYCODE_VOLUME_MUTE -> {
onKeyClick(keyCode)
true }
KEYCODE_BACK -> {
// onBackPressed()
this.onBackPressed()
false
}
KEYCODE_MEDIA_FAST_FORWARD -> { true }
KEYCODE_MEDIA_REWIND -> { true }
KEYCODE_MEDIA_PREVIOUS -> { true }
KEYCODE_MEDIA_NEXT -> { true }
else -> {
super.onKeyDown(keyCode, event)
}
}
}
open fun onKeyClick(keyCode: Int) : Boolean {
when(keyCode) {
KEYCODE_VOLUME_DOWN -> {
true
}
KEYCODE_VOLUME_UP -> {
true
}
KEYCODE_VOLUME_MUTE -> {
}
KEYCODE_MEDIA_FAST_FORWARD -> { true }
KEYCODE_MEDIA_REWIND -> { true }
KEYCODE_MEDIA_PREVIOUS -> { true }
KEYCODE_MEDIA_NEXT -> { true }
else -> {
}
}
return false
}
fun openRealm() : Realm = HistoryManager.openRealm
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
package com.mime.dualscreenview.activity
import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
class Main : Base() {
}
@@ -0,0 +1,123 @@
package com.mime.dualscreenview.activity
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.databinding.DataBindingUtil
import com.mime.dualscreenview.R
import com.mime.dualscreenview.common.PairArray
import com.mime.dualscreenview.common.colorz
import com.mime.dualscreenview.common.getIndex
import com.mime.dualscreenview.common.typesfacez
import com.mime.dualscreenview.data.HistoryManager
import com.mime.dualscreenview.data.model.ReaderConfig
import com.mime.dualscreenview.databinding.SettingsBinding
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.ext.query
class Settings : Base() {
lateinit var binding : SettingsBinding
var readerConfig : ReaderConfig? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.settings)
// setContentView(R.layout.settings)
var realm = HistoryManager.openRealm
realm.writeBlocking {
this.query<ReaderConfig>()?.find()?.let {
if (it.size > 0) {
readerConfig = copyFromRealm(it.first())
} else {
readerConfig = ReaderConfig()
}
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onResume() {
super.onResume()
binding.textSize.displayFormat = "글자 크기 : %d"
binding.textSize.value = readerConfig?.textSize ?: 14
binding.textSize.mValueChange = {
binding.preview.textSize = it.toFloat() ;
if (readerConfig?.textSize != it) {
readerConfig?.textSize = it
configSave()
}
}
binding.pagePadding.displayFormat = "페이지 여백 : %d"
binding.pagePadding.value = readerConfig?.padding ?: 5
binding.pagePadding.mValueChange = {
binding.preview.setPadding(it,it,it,it) ;
if (readerConfig?.padding != it) {
readerConfig?.padding = it
configSave()
}
}
binding.letterSpace.displayFormat = "자간 : %d"
binding.letterSpace.value = readerConfig?.letterSpace ?: 1
binding.letterSpace.mValueChange = {
binding.preview.letterSpacing = it.times(0.01).toFloat() ;
if (readerConfig?.letterSpace != it) {
readerConfig?.letterSpace = it
configSave()
}
}
binding.lineSpace.displayFormat = "행간 : %d"
binding.lineSpace.value = readerConfig?.lineSpace ?: 1
binding.lineSpace.mValueChange = {
binding.preview.setLineSpacing(1f, 1f.plus(it.times(0.01f))) ;
if (readerConfig?.lineSpace != it) {
readerConfig?.lineSpace = it
configSave()
}
}
binding.pageTypesface.displayFormat = "폰트 : %s"
binding.pageTypesface.titleArray = typesfacez.map { it.first }.toTypedArray()
binding.pageTypesface.value = getIndex(typesfacez as PairArray<Any>,readerConfig?.font ?: "")
binding.pageTypesface.mValueChange = {
val pair = typesfacez.get(it)
binding.preview.setTypeface(resources.getFont(pair.second))
if (readerConfig?.font != pair.first) {
readerConfig?.font = pair.first ?: ""
configSave()
}
}
binding.pageStyle.displayFormat = "스타일 : %s"
binding.pageStyle.titleArray = colorz.map { it.first }.toTypedArray()
binding.pageStyle.value = readerConfig?.style ?: 0
binding.pageStyle.mValueChange = {
val pair = colorz.get(it)
binding.preview.setBackgroundColor(Color.parseColor(pair.second.last()))
binding.preview.setTextColor(Color.parseColor(pair.second.first()))
if (readerConfig?.style != it) {
readerConfig?.style = it ?: 0
configSave()
}
}
}
fun configSave() {
var realm = HistoryManager.openRealm
realm.writeBlocking {
copyToRealm(readerConfig!!, UpdatePolicy.ALL)
}
}
}
@@ -0,0 +1,54 @@
package com.mime.dualscreenview.common
import android.util.Log
import com.mime.dualscreenview.BuildConfig
import java.lang.Exception
object Blog {
val DEFAULT_TAG = "MyEBook_TAG"
enum class BLogType {
D,I,E
}
fun LOGD(tag : String = DEFAULT_TAG, log: String){
LOG(BLogType.D,tag,log)
}
fun LOGI(tag : String = DEFAULT_TAG, log: String){
LOG(BLogType.I,tag,log)
}
fun LOGE(tag : String = DEFAULT_TAG, log: Throwable){
LOG(BLogType.E,tag,log.toString())
}
fun LOGE(tag : String = DEFAULT_TAG, log: Exception){
LOG(BLogType.E,tag,log.toString())
}
fun LOGE(tag : String = DEFAULT_TAG, log: String){
LOG(BLogType.E,tag,log)
}
fun LOGE(log: String){
LOG(BLogType.E,DEFAULT_TAG,log)
}
private fun LOG(type : BLogType, tag : String, log : String) {
if (BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.contains("debug")) {
when(type) {
BLogType.D -> {
Log.d(tag,log)
}
BLogType.I -> {
Log.i(tag,log)
}
BLogType.E -> {
Log.e(tag,log)
}
else -> {}
}
}
}
}
@@ -0,0 +1,39 @@
package com.mime.dualscreenview.common
import com.mime.dualscreenview.R
typealias PairArray<T> = Array<Pair<String,T>>
val colorz : PairArray<Array<String>> = arrayOf<Pair<String,Array<String>>>(
Pair("color set 01",arrayOf<String>("#E1F5FE", "#263238")),
Pair("color set 02",arrayOf<String>("#F0F4C3", "#37474F")),
Pair("color set 03",arrayOf<String>("#ECEFF1", "#455A64")),
Pair("color set 04",arrayOf<String>("#E0F7FA", "#263238")),
Pair("color set 05",arrayOf<String>("#F5F5F5", "#263238")),
Pair("color set 06",arrayOf<String>("#ECEFF1", "#263238")),
Pair("color set 07",arrayOf<String>("#F8BBD0", "#263238")),
Pair("color set 08",arrayOf<String>("#E6EE9C", "#455A64")),
Pair("color set 09",arrayOf<String>("#CFD8DC", "#455A64")),
Pair("color set 10",arrayOf<String>("#FFF59D", "#37474F"))
)
val typesfacez : PairArray<Int> = arrayOf<Pair<String,Int>>(
Pair("정선 아리랑 혼", R.font.jsarirang_hon),
Pair("정선 아리랑 뿌리", R.font.jsarirang_ppuri),
Pair("정선 동강 레귤러", R.font.jsdongkang_regular),
Pair("손기정체", R.font.kcc_sonkeechung),
Pair("교보 손글씨", R.font.kyobo_handwriting_2021sjy),
Pair("태백 은하수", R.font.taebaek_milkyway),
)
@JvmName("getIndexAny")
fun PairArray<Any>.getIndex(key : String) = getIndex(this, key)
fun getIndex(collection : PairArray<Any>, key : String) : Int {
var index = 0
var returns = 0
for (item in collection) {
if(item.first.equals(key)) {
returns = index
}
index = index.inc()
}
return returns
}
@@ -0,0 +1,143 @@
package com.mime.dualscreenview.data
import com.mime.dualscreenview.common.Blog
import com.mime.dualscreenview.data.model.BookPageInfo
import com.mime.dualscreenview.data.model.BookPageInfos
import com.mime.dualscreenview.data.model.HistoryItem
import com.mime.dualscreenview.data.model.LastInfo
import com.mime.dualscreenview.data.model.ReaderConfig
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.log.LogLevel
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass
object HistoryManager {
val clazz : Set<KClass<out BaseRealmObject>> = setOf(LastInfo::class, HistoryItem::class, ReaderConfig::class, BookPageInfos::class, BookPageInfo::class)
val schemaVersion : Long = 5
val openRealm : Realm = Realm.open(RealmConfiguration.Builder(clazz as Set<KClass<out TypedRealmObject>>)
.schemaVersion(schemaVersion)
.deleteRealmIfMigrationNeeded()
.build())
fun save(lastInfo: LastInfo) {
openRealm.apply{
this.writeBlocking {
copyToRealm(lastInfo, UpdatePolicy.ALL)
}
}
}
fun save(historyItem: HistoryItem) {
openRealm.apply{
this.writeBlocking {
copyToRealm(historyItem, UpdatePolicy.ALL)
}
}
}
fun requestLastInfo(callback : (LastInfo)->Unit) {
openRealm.apply{
}
}
fun save(config : ReaderConfig) {
openRealm.apply{
this.writeBlocking {
copyToRealm(config,UpdatePolicy.ALL)
}
}
}
fun getBooInfo(url : String ,callback : (BookPageInfos?)->Unit) {
openRealm.apply{
Blog.LOGE("get ${url}" )
Blog.LOGE("get ${this.query(BookPageInfo::class).count().find()}" )
var bookPageInfo = this.query(BookPageInfo::class).query("pathUrl == $0","${url}").find()
if (bookPageInfo != null && bookPageInfo.count() > 0) {
Blog.LOGE("get ${bookPageInfo}" )
this.query(BookPageInfos::class,"bookPageUrl == $0",bookPageInfo.first().bookPageUrl).find().first().let {
Blog.LOGE("get ${it} , ${it?.pages}" )
callback.invoke(this.copyFromRealm(it))
}
} else {
callback.invoke(null)
}
}
}
fun getBooPageInfo(url : String ,callback : (BookPageInfo?)->Unit) {
openRealm.apply{
Blog.LOGE("get ${url}" )
Blog.LOGE("get ${this.query(BookPageInfo::class).count().find()}" )
var result = this.query(BookPageInfo::class).query("pathUrl == $0","${url}").find()
if (result.size > 0) {
var bookPageInfo = result?.first()
if (bookPageInfo != null) {
callback.invoke(bookPageInfo)
}
}
}
}
fun getBooPageInfoContentsSave(url : String, contents : String) {
openRealm.writeBlocking {
Blog.LOGE("get ${url}")
Blog.LOGE("get ${query(BookPageInfo::class).count().find()}")
val result = query(BookPageInfo::class).query("pathUrl == $0", "${url}").find()
if (result.size > 0) {
result.first().contents = contents
copyToRealm(result.first(), UpdatePolicy.ALL)
}
}
}
fun getNextPage(url : String ,callback : (BookPageInfo?)->Unit) {
openRealm.apply{
Blog.LOGE("getNextPage ${url}" )
Blog.LOGE("getNextPage ${this.query(BookPageInfo::class).count().find()}" )
var bookPageInfo = this.query(BookPageInfo::class).query("pathUrl == $0","${url}").find()?.first()
Blog.LOGE("getNextPage 1 ${bookPageInfo}" )
if (bookPageInfo != null) {
Blog.LOGE("getNextPage 2 ${bookPageInfo.chapterNum}" )
Blog.LOGE("getNextPage 2 ${bookPageInfo.bookPageUrl}" )
var results = this.query(BookPageInfo::class).query("chapterNum == $0",bookPageInfo.chapterNum + 1).query("bookPageUrl == $0","${bookPageInfo.bookPageUrl}").find()
if(results.size > 0) {
results.first().let {
Blog.LOGE("getNextPage 2 ${it.pathUrl}" )
callback.invoke(it)
}
}
}
}
}
fun getPrevPage(url : String ,callback : (BookPageInfo?)->Unit) {
openRealm.apply{
Blog.LOGE("getPrevPage ${url}" )
Blog.LOGE("getPrevPage ${this.query(BookPageInfo::class).count().find()}" )
var bookPageInfo = this.query(BookPageInfo::class).query("pathUrl == $0","${url}").find()?.first()
Blog.LOGE("getNextPage ${bookPageInfo}" )
if (bookPageInfo != null) {
Blog.LOGE("getNextPage 2 ${bookPageInfo.chapterNum}" )
Blog.LOGE("getNextPage 2 ${bookPageInfo.bookPageUrl}" )
var results = this.query(BookPageInfo::class).query("chapterNum == $0 && bookPageUrl == $1",bookPageInfo.chapterNum - 1,"${bookPageInfo.bookPageUrl}").find()
if(results.size > 0) {
results.first()?.let {
callback.invoke(it)
}
}
}
}
}
}
@@ -0,0 +1,121 @@
package com.mime.dualscreenview.data.model
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.Ignore
import io.realm.kotlin.types.annotations.PrimaryKey
class BookPageInfosJ {
var bookTitle : String = ""
var bookPageUrl : String = ""
var pages : ArrayList<BookPageInfoJ> = arrayListOf<BookPageInfoJ>()
fun getTitleArray() : ArrayList<String> {
var arrayList = ArrayList<String>()
pages.forEach { arrayList.add(it.bookTitle ?: "") }
return arrayList
}
fun getR() : BookPageInfos{
var r = BookPageInfos()
r.bookTitle = this.bookTitle
r.bookPageUrl = this.bookPageUrl
return r
}
}
class BookPageInfoJ {
var chapterID : Int = 0
var contents : String? = ""
var bookPageUrl : String? = ""
var chapterTitle : String? = ""
var bookTitle : String? = ""
var chapterNum : Int = 0
var lastPage : Int? = 0
var pathUrl : String? = ""
fun getRealm() : BookPageInfo {
var r = BookPageInfo()
r.chapterID = this.chapterID
r.contents = this.contents
r.bookPageUrl = this.bookPageUrl ?: ""
r.chapterTitle = this.chapterTitle
r.bookTitle = this.bookTitle ?: ""
r.chapterNum = this.chapterNum
r.lastPage = this.lastPage
r.pathUrl = this.pathUrl?.replace("'","")
return r
}
}
class BookPageInfos : RealmObject {
var bookTitle : String = ""
@PrimaryKey
var bookPageUrl : String? = ""
var pages : RealmList<BookPageInfo> = realmListOf()
fun getTitleArray() : ArrayList<String> {
var arrayList = ArrayList<String>()
pages.forEach {
arrayList.add(it.getTitleItem())}
return arrayList
}
fun sort() {
val comparator : Comparator<BookPageInfo> = compareBy { it.chapterID }
pages.sortWith(comparator)
}
fun hasItem(item: BookPageInfo) : Boolean {
var hasItem = false
for (c in pages) {
if (!hasItem) {
hasItem = c.pathUrl.equals(item.pathUrl)
}
}
return hasItem
}
}
class BookPageInfo : RealmObject {
var chapterID : Int = 0
var contents : String? = ""
var bookPageUrl : String? = ""
var chapterTitle : String? = ""
var bookTitle : String? = ""
var chapterNum : Int = 0
var lastPage : Int? = 0
@PrimaryKey
var pathUrl : String? = ""
fun getTitleItem() : String {
var result = StringBuilder()
result.append(if ((contents?.length ?: 0) > 10) {
"S:[0] "
} else {
"[X] "
})
result.append(chapterTitle?: "")
result.append(if ((lastPage ?: 0) > 0) {
" [0] "
} else {
" [X] "
})
return result.toString()
}
}
@@ -0,0 +1,61 @@
package com.mime.dualscreenview.data.model
import android.content.pm.ActivityInfo
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
import java.text.SimpleDateFormat
import java.util.Date
class LastInfo : RealmObject {
@PrimaryKey
var _id : String = "UniqLastId"
var pageUrl : String = ""
var title : String = ""
var chapter : Int = 0
var pageIndex : Int = 0
var contentsName : String = ""
var displayOrientation : Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
fun makeHistoryItem() : HistoryItem = HistoryItem().apply {
title = this@LastInfo.title
pageUrl = this@LastInfo.pageUrl
chapter = this@LastInfo.chapter
pageIndex = this@LastInfo.pageIndex
contentsName = this@LastInfo.contentsName
displayOrientation = this@LastInfo.displayOrientation
}
}
class HistoryItem : RealmObject {
@PrimaryKey
var title : String = ""
var pageUrl : String = ""
var chapter : Int = 0
var pageIndex : Int = 0
var contentsName : String = ""
var displayOrientation : Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
fun makeLastInfo() = LastInfo().apply{
_id = "UniqLastId"
title = this@HistoryItem.title
pageUrl = this@HistoryItem.pageUrl
chapter = this@HistoryItem.chapter
pageIndex = this@HistoryItem.pageIndex
contentsName = this@HistoryItem.contentsName
displayOrientation = this@HistoryItem.displayOrientation
}
fun putHistory(bookPageInfo: BookPageInfo? , currentPath : String) : HistoryItem {
title = bookPageInfo?.bookTitle ?: SimpleDateFormat("YY-mm-DD-HH:mm").format(Date())
pageUrl = bookPageInfo?.pathUrl ?: ""
chapter = bookPageInfo?.chapterNum ?: 0
pageIndex = bookPageInfo?.lastPage ?: 0
contentsName = bookPageInfo?.chapterTitle ?: ""
return this
}
}
class Bookmark() : RealmObject {
@PrimaryKey
var pageUrl : String = ""
}
@@ -0,0 +1,17 @@
package com.mime.dualscreenview.data.model
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
class ReaderConfig : RealmObject {
@PrimaryKey
var id : String? = "ReaderConfig"
var textSize : Int? = 14
var textColor : String? = "#FFFFFF"
var bgColor : String? = "#000000"
var style : Int? = 0
var lineSpace : Int? = 1
var letterSpace : Int? = 1
var font : String? = ""
var padding : Int? = 5
}
@@ -0,0 +1,55 @@
package com.mime.dualscreenview.dialog
import android.R
import android.content.Context
import android.content.DialogInterface
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import com.mime.dualscreenview.data.model.BookPageInfo
object DefaultList {
fun showDefaultList(context: Context, title : String, items : Collection<BookPageInfo>, firstPosition : Int, choosedTitle : (Int)->String, chooedPositive : (Int)->Unit, saveCalback : (Int)->Unit ) {
val builderSingle: AlertDialog.Builder = AlertDialog.Builder(context)
builderSingle.setTitle(title)
val arrayAdapter =
ArrayAdapter<String>(context, R.layout.select_dialog_singlechoice)
for (item in items) {
arrayAdapter.add(item.getTitleItem())
}
builderSingle.setNeutralButton("전체 저장") { dialog, which ->
saveCalback.invoke(-1)
dialog.dismiss()
}
builderSingle.setNegativeButton("닫기",
DialogInterface.OnClickListener { dialog, which ->
dialog.dismiss() })
builderSingle.setAdapter(arrayAdapter,
DialogInterface.OnClickListener { dialog, position ->
val strName = arrayAdapter.getItem(position)
val builderInner: AlertDialog.Builder = AlertDialog.Builder(context)
builderInner.setMessage(strName)
builderInner.setTitle(choosedTitle.invoke(position))
builderInner.setNegativeButton("닫기") { dialog, which ->
}
// builderInner.setNeutralButton("자동 저장") { dialog, which ->
// saveCalback.invoke(position)
// }
builderInner.setPositiveButton("이동"){ dialog, which ->
chooedPositive.invoke(position)
dialog.dismiss()
}
builderInner.show().apply {
DialogManager.add(this)
}
})
builderSingle.create()?.apply{
setOnShowListener { d->
(d as? AlertDialog)?.let{
it.listView?.setSelection(firstPosition)
}
}
DialogManager.add(this)
}?.show()
}
}
@@ -0,0 +1,19 @@
package com.mime.dualscreenview.dialog
import android.app.Dialog
import androidx.activity.ComponentDialog
import java.lang.Exception
object DialogManager {
val dialogs = arrayListOf<ComponentDialog>()
fun add(item : ComponentDialog) = dialogs.add(item)
fun closeAll() {
for (dialog in dialogs) {
try {
dialog?.dismiss()
} catch(e : Exception) {
}
}
}
}
@@ -0,0 +1,97 @@
//package com.mime.dualscreenview.dialog
//
//import android.app.Dialog
//import android.content.Context
//import android.content.DialogInterface
//import android.graphics.Color
//import android.os.Bundle
//import android.view.LayoutInflater
//import android.view.View
//import android.view.ViewGroup
//import android.view.Window
//import android.widget.TextView
//import androidx.recyclerview.widget.GridLayoutManager
//import androidx.recyclerview.widget.RecyclerView
//import com.mime.dualscreenview.R
//import kotlin.random.Random
//
//interface StyleSelectInterface {
// fun onSelectStyle(bgColor : String, textColor : String)
//}
//class Stylez : Dialog {
// constructor(context: Context) : super(context) {initView(context)}
// constructor(context: Context, themeResId: Int) : super(context, themeResId) {initView(context)}
// constructor(
// context: Context,
// cancelable: Boolean,
// cancelListener: DialogInterface.OnCancelListener?
// ) : super(context, cancelable, cancelListener) {initView(context)}
//
// var styleSelectInterface : StyleSelectInterface? = null
//
// fun initView(context: Context) {
//
// }
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// setCancelable(true)
// setContentView(R.layout.dialog_stylesz)
// val recyclerView: RecyclerView = findViewById(R.id.stylez_recyclerview)
// val adapterRe = AdapterRe(context, colorz)
// recyclerView.adapter = adapterRe
// recyclerView.layoutManager =
// GridLayoutManager(context, 2, GridLayoutManager.VERTICAL, false)
// }
//
// inner class AdapterRe(ctx: Context, myImageNameList: Array<Array<String>>) :
// RecyclerView.Adapter<StylezViewHolder>() {
// private val inflater: LayoutInflater
// private val myImageNameList: Array<Array<String>>
// private val ctx : Context
// init {
// inflater = LayoutInflater.from(ctx)
// this.ctx = ctx
// this.myImageNameList = myImageNameList
// }
//
// override fun onCreateViewHolder (
// parent: ViewGroup,
// viewType: Int
// ): StylezViewHolder {
// val view: View = inflater.inflate(R.layout.item_colorz, parent, false)
// return StylezViewHolder(view)
// }
//
// override fun onBindViewHolder(holder: StylezViewHolder, position: Int) {
// var colorz = myImageNameList.get(position)
// var sampleTextz = ctx.resources.getStringArray(R.array.sample_textz)
// holder?.setStyle(bgColor = colorz.get(1), textColor = colorz.get(0),sampleTextz.get(Random.nextInt(9876) % sampleTextz.size))
// }
//
//
// override fun getItemCount(): Int {
// return myImageNameList.size
// }
//
// }
//
// inner class StylezViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// var textView: TextView
// var bg : View
// init {
// textView = itemView.findViewById(R.id.textview_sample)
// bg = itemView.findViewById(R.id.bg_sample)
// }
// fun setStyle(bgColor : String, textColor : String, sampleText : String ) {
// bg.setBackgroundColor(Color.parseColor(bgColor))
// textView.setTextColor(Color.parseColor(textColor))
// textView.text = sampleText
// itemView?.setOnClickListener { v ->
// styleSelectInterface?.onSelectStyle(bgColor,textColor)
// dismiss()
// }
// }
// }
//}
@@ -0,0 +1,815 @@
package com.mime.dualscreenview.view
import android.content.Context
import android.os.SystemClock
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import com.mime.dualscreenview.common.Blog
import com.mime.dualscreenview.view.GestureAnalyser.GestureType
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sqrt
enum class TouchArea {
Left,Center,Right, DoubleLeft, DoubleRight
}
class GestureAnalyser @JvmOverloads constructor(
swipeSlopeIntolerance: Int = 3,
doubleTapMaxDelayMillis: Int = 500,
doubleTapMaxDownMillis: Int = 100
) {
private val initialX = DoubleArray(5)
private val initialY = DoubleArray(5)
private val finalX = DoubleArray(5)
private val finalY = DoubleArray(5)
private val currentX = DoubleArray(5)
private val currentY = DoubleArray(5)
private val delX = DoubleArray(5)
private val delY = DoubleArray(5)
private var numFingers = 0
private var initialT: Long = 0
private var finalT: Long = 0
private var currentT: Long = 0
private var prevInitialT: Long = 0
private var prevFinalT: Long = 0
private var swipeSlopeIntolerance = 3
private val doubleTapMaxDelayMillis: Long
private val doubleTapMaxDownMillis: Long
init {
this.swipeSlopeIntolerance = swipeSlopeIntolerance
this.doubleTapMaxDownMillis = doubleTapMaxDownMillis.toLong()
this.doubleTapMaxDelayMillis = doubleTapMaxDelayMillis.toLong()
}
fun trackGesture(ev: MotionEvent) {
val n = ev.pointerCount
for (i in 0 until n) {
initialX[i] = ev.getX(i).toDouble()
initialY[i] = ev.getY(i).toDouble()
}
numFingers = n
initialT = SystemClock.uptimeMillis()
}
fun untrackGesture() {
numFingers = 0
prevFinalT = SystemClock.uptimeMillis()
prevInitialT = initialT
}
fun getGesture(ev: MotionEvent): GestureType {
var averageDistance = 0.0
for (i in 0 until numFingers) {
finalX[i] = ev.getX(i).toDouble()
finalY[i] = ev.getY(i).toDouble()
delX[i] = finalX[i] - initialX[i]
delY[i] = finalY[i] - initialY[i]
averageDistance += sqrt(
(finalX[i] - initialX[i]).pow(2.0) + (finalY[i] - initialY[i]).pow(
2.0
)
)
}
averageDistance /= numFingers.toDouble()
finalT = SystemClock.uptimeMillis()
val gt = GestureType()
gt.gestureFlag = calcGesture()
gt.gestureDuration = finalT - initialT
gt.gestureDistance = averageDistance
return gt
}
fun getOngoingGesture(ev: MotionEvent): Int {
for (i in 0 until numFingers) {
currentX[i] = ev.getX(i).toDouble()
currentY[i] = ev.getY(i).toDouble()
delX[i] = finalX[i] - initialX[i]
delY[i] = finalY[i] - initialY[i]
}
currentT = SystemClock.uptimeMillis()
return calcGesture()
}
private fun calcGesture(): Int {
if (isDoubleTap) {
return DOUBLE_TAP_1
}
if (numFingers == 1) {
if ((-(delY[0])) > (swipeSlopeIntolerance * (abs(
delX[0]
)))
) {
return SWIPE_1_UP
}
if (((delY[0])) > (swipeSlopeIntolerance * (abs(
delX[0]
)))
) {
return SWIPE_1_DOWN
}
if ((-(delX[0])) > (swipeSlopeIntolerance * (abs(
delY[0]
)))
) {
return SWIPE_1_LEFT
}
if (((delX[0])) > (swipeSlopeIntolerance * (abs(
delY[0]
)))
) {
return SWIPE_1_RIGHT
}
}
if (numFingers == 2) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
))) && ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
) {
return SWIPE_2_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
))) && ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
) {
return SWIPE_2_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
))) && ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
) {
return SWIPE_2_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
))) && ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
) {
return SWIPE_2_RIGHT
}
if (finalFingDist(0, 1) > 2 * (initialFingDist(0, 1))) {
return UNPINCH_2
}
if (finalFingDist(0, 1) < 0.5 * (initialFingDist(0, 1))) {
return PINCH_2
}
}
if (numFingers == 3) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
) {
return SWIPE_3_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
) {
return SWIPE_3_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
) {
return SWIPE_3_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
) {
return SWIPE_3_RIGHT
}
if ((finalFingDist(0, 1) > 1.75 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) > 1.75 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 0) > 1.75 * (initialFingDist(2, 0)))
) {
return UNPINCH_3
}
if ((finalFingDist(0, 1) < 0.66 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) < 0.66 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 0) < 0.66 * (initialFingDist(2, 0)))
) {
return PINCH_3
}
}
if (numFingers == 4) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
&& ((-delY[3]) > (swipeSlopeIntolerance * abs(
delX[3]
)))
) {
return SWIPE_4_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
&& ((delY[3]) > (swipeSlopeIntolerance * abs(
delX[3]
)))
) {
return SWIPE_4_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
&& ((-delX[3]) > (swipeSlopeIntolerance * abs(
delY[3]
)))
) {
return SWIPE_4_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
&& ((delX[3]) > (swipeSlopeIntolerance * abs(
delY[3]
)))
) {
return SWIPE_4_RIGHT
}
if ((finalFingDist(0, 1) > 1.5 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) > 1.5 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 3) > 1.5 * (initialFingDist(2, 3)))
&& (finalFingDist(3, 0) > 1.5 * (initialFingDist(3, 0)))
) {
return UNPINCH_4
}
if ((finalFingDist(0, 1) < 0.8 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) < 0.8 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 3) < 0.8 * (initialFingDist(2, 3)))
&& (finalFingDist(3, 0) < 0.8 * (initialFingDist(3, 0)))
) {
return PINCH_4
}
}
return 0
}
private fun initialFingDist(fingNum1: Int, fingNum2: Int): Double {
return sqrt(
(initialX[fingNum1] - initialX[fingNum2]).pow(2.0) + (initialY[fingNum1] - initialY[fingNum2]).pow(
2.0
)
)
}
private fun finalFingDist(fingNum1: Int, fingNum2: Int): Double {
return sqrt(
(finalX[fingNum1] - finalX[fingNum2]).pow(2.0) + (finalY[fingNum1] - finalY[fingNum2]).pow(
2.0
)
)
}
val isDoubleTap: Boolean
get() = if (initialT - prevFinalT < doubleTapMaxDelayMillis && finalT - initialT < doubleTapMaxDownMillis && prevFinalT - prevInitialT < doubleTapMaxDownMillis) {
true
} else {
false
}
inner class GestureType {
var gestureFlag: Int = 0
var gestureDuration: Long = 0
var gestureDistance: Double = 0.0
}
companion object {
const val DEBUG: Boolean = true
// Finished gestures flags
const val SWIPE_1_UP: Int = 11
const val SWIPE_1_DOWN: Int = 12
const val SWIPE_1_LEFT: Int = 13
const val SWIPE_1_RIGHT: Int = 14
const val SWIPE_2_UP: Int = 21
const val SWIPE_2_DOWN: Int = 22
const val SWIPE_2_LEFT: Int = 23
const val SWIPE_2_RIGHT: Int = 24
const val SWIPE_3_UP: Int = 31
const val SWIPE_3_DOWN: Int = 32
const val SWIPE_3_LEFT: Int = 33
const val SWIPE_3_RIGHT: Int = 34
const val SWIPE_4_UP: Int = 41
const val SWIPE_4_DOWN: Int = 42
const val SWIPE_4_LEFT: Int = 43
const val SWIPE_4_RIGHT: Int = 44
const val PINCH_2: Int = 25
const val UNPINCH_2: Int = 26
const val PINCH_3: Int = 35
const val UNPINCH_3: Int = 36
const val PINCH_4: Int = 45
const val UNPINCH_4: Int = 46
const val DOUBLE_TAP_1: Int = 107
//Ongoing gesture flags
const val SWIPING_1_UP: Int = 101
const val SWIPING_1_DOWN: Int = 102
const val SWIPING_1_LEFT: Int = 103
const val SWIPING_1_RIGHT: Int = 104
const val SWIPING_2_UP: Int = 201
const val SWIPING_2_DOWN: Int = 202
const val SWIPING_2_LEFT: Int = 203
const val SWIPING_2_RIGHT: Int = 204
const val PINCHING: Int = 205
const val UNPINCHING: Int = 206
private const val TAG = "GestureAnalyser"
}
}
class SimpleFingerGestures : OnTouchListener {
private var debug = true
var consumeTouchEvents: Boolean = false
protected var tracking: BooleanArray = booleanArrayOf(false, false, false, false, false)
private var ga: GestureAnalyser
private var onFingerGestureListener: OnFingerGestureListener? = null
/**
* Constructor that creates an internal [in.championswimmer.sfg.lib.GestureAnalyser] object as well
*/
constructor() {
ga = GestureAnalyser()
}
constructor(
swipeSlopeIntolerance: Int,
doubleTapMaxDelayMillis: Int,
doubleTapMaxDownMillis: Int
) {
ga = GestureAnalyser(swipeSlopeIntolerance, doubleTapMaxDelayMillis, doubleTapMaxDownMillis)
}
fun setDebug(debug: Boolean) {
this.debug = debug
}
constructor(omfgl: OnFingerGestureListener?) {
ga = GestureAnalyser()
setOnFingerGestureListener(omfgl)
}
/**
* Register a callback to be invoked when multi-finger gestures take place
*
*
* <br></br>
*
*
* For the callbacks implemented via this, check the interface [in.championswimmer.sfg.lib.SimpleFingerGestures.OnFingerGestureListener]
*
*
* @param omfgl The callback that will run
*/
fun setOnFingerGestureListener(omfgl: OnFingerGestureListener?) {
onFingerGestureListener = omfgl
}
override fun onTouch(view: View, ev: MotionEvent): Boolean {
if (debug) Log.d(TAG, "onTouch")
when (ev.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
if (debug) Log.d(TAG, "ACTION_DOWN")
startTracking(0)
ga.trackGesture(ev)
return consumeTouchEvents
}
MotionEvent.ACTION_UP -> {
if (debug) Log.d(TAG, "ACTION_UP")
if (tracking[0]) {
doCallBack(ga.getGesture(ev))
}
stopTracking(0)
ga.untrackGesture()
return consumeTouchEvents
}
MotionEvent.ACTION_POINTER_DOWN -> {
if (debug) Log.d(TAG, "ACTION_POINTER_DOWN" + " " + "num" + ev.pointerCount)
startTracking(ev.pointerCount - 1)
ga.trackGesture(ev)
return consumeTouchEvents
}
MotionEvent.ACTION_POINTER_UP -> {
if (debug) Log.d(TAG, "ACTION_POINTER_UP" + " " + "num" + ev.pointerCount)
if (tracking[1]) {
doCallBack(ga.getGesture(ev))
}
stopTracking(ev.pointerCount - 1)
ga.untrackGesture()
return consumeTouchEvents
}
MotionEvent.ACTION_CANCEL -> {
if (debug) Log.d(TAG, "ACTION_CANCEL")
return true
}
MotionEvent.ACTION_MOVE -> {
if (debug) Log.d(TAG, "ACTION_MOVE")
return consumeTouchEvents
}
}
return consumeTouchEvents
}
private fun doCallBack(mGt: GestureType) {
when (mGt.gestureFlag) {
GestureAnalyser.SWIPE_1_UP -> onFingerGestureListener!!.onSwipeUp(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_DOWN -> onFingerGestureListener!!.onSwipeDown(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_LEFT -> onFingerGestureListener!!.onSwipeLeft(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_RIGHT -> onFingerGestureListener!!.onSwipeRight(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_UP -> onFingerGestureListener!!.onSwipeUp(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_DOWN -> onFingerGestureListener!!.onSwipeDown(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_LEFT -> onFingerGestureListener!!.onSwipeLeft(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_RIGHT -> onFingerGestureListener!!.onSwipeRight(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_2 -> onFingerGestureListener!!.onPinch(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_2 -> onFingerGestureListener!!.onUnpinch(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_UP -> onFingerGestureListener!!.onSwipeUp(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_DOWN -> onFingerGestureListener!!.onSwipeDown(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_LEFT -> onFingerGestureListener!!.onSwipeLeft(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_RIGHT -> onFingerGestureListener!!.onSwipeRight(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_3 -> onFingerGestureListener!!.onPinch(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_3 -> onFingerGestureListener!!.onUnpinch(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_UP -> onFingerGestureListener!!.onSwipeUp(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_DOWN -> onFingerGestureListener!!.onSwipeDown(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_LEFT -> onFingerGestureListener!!.onSwipeLeft(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_RIGHT -> onFingerGestureListener!!.onSwipeRight(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_4 -> onFingerGestureListener!!.onPinch(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_4 -> {
onFingerGestureListener!!.onUnpinch(4, mGt.gestureDuration, mGt.gestureDistance)
onFingerGestureListener!!.onDoubleTap(1)
}
GestureAnalyser.DOUBLE_TAP_1 -> onFingerGestureListener!!.onDoubleTap(1)
}
}
private fun startTracking(nthPointer: Int) {
for (i in 0..nthPointer) {
tracking[i] = true
}
}
private fun stopTracking(nthPointer: Int) {
for (i in nthPointer until tracking.size) {
tracking[i] = false
}
}
/**
* Interface definition for the callback to be invoked when 2-finger gestures are performed
*/
interface OnFingerGestureListener {
/**
* Called when user swipes **up** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeUp(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **down** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeDown(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **left** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeLeft(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **right** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeRight(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user **pinches** with two fingers (bring together)
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onPinch(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user **un-pinches** with two fingers (take apart)
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onUnpinch(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
fun onDoubleTap(fingers: Int): Boolean
}
companion object {
// Will see if these need to be used. For now just returning duration in milliS
const val GESTURE_SPEED_SLOW: Long = 1500
const val GESTURE_SPEED_MEDIUM: Long = 1000
const val GESTURE_SPEED_FAST: Long = 500
private const val TAG = "SimpleFingerGestures"
}
}
//abstract class OnSwipeTouchListener(val context: Context?) : View.OnTouchListener {
// companion object {
// private const val SWIPE_DISTANCE_THRESHOLD = 100
// private const val SWIPE_VELOCITY_THRESHOLD = 100
// }
// private val gestureDetector: GestureDetector
// abstract fun onSwipeLeft()
// abstract fun onSwipeRight()
// abstract fun onSwipeDown()
// abstract fun onSwipeUp()
// abstract fun onSingleTap(area : TouchArea)
// abstract fun onDoubleTap(area : TouchArea)
//
// override fun onTouch(v: View?, event: MotionEvent?): Boolean {
// if (event == null) {
// return false
// }
// return gestureDetector.onTouchEvent(event!!)
// }
//
//
// private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
//
// override fun onDown(e: MotionEvent): Boolean {
// return true
// }
//
// override fun onDoubleTapEvent(e: MotionEvent): Boolean {
// val width: Int = context?.resources?.displayMetrics?.widthPixels ?: 0
// val height: Int = context?.resources?.displayMetrics?.heightPixels ?: 0
// var touchArea : TouchArea = TouchArea.Center
// if(width > 0 && height > 0) {
// val centerAreaSize = width * 0.4
// var sideAreaSize = (width - centerAreaSize) * 0.5
// if(e.x < sideAreaSize) {
// touchArea = TouchArea.DoubleLeft
// } else if(e.x > sideAreaSize && e.x < width - sideAreaSize) {
//
// } else {
// touchArea = TouchArea.DoubleRight
// }
// }
// onDoubleTap(touchArea)
// return super.onDoubleTapEvent(e)
// }
//
// override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
// val width: Int = context?.resources?.displayMetrics?.widthPixels ?: 0
// val height: Int = context?.resources?.displayMetrics?.heightPixels ?: 0
// var touchArea : TouchArea = TouchArea.Center
// if(width > 0 && height > 0) {
// val centerAreaSize = width * 0.4
// var sideAreaSize = (width - centerAreaSize) * 0.5
// if(e.x < sideAreaSize) {
// touchArea = TouchArea.Left
// } else if(e.x > sideAreaSize && e.x < width - sideAreaSize) {
//
// } else {
// touchArea = TouchArea.Right
// }
// }
// onSingleTap(touchArea)
// return super.onSingleTapConfirmed(e)
// }
//
//// override fun onSingleTapUp(e: MotionEvent): Boolean {
////
//// return super.onSingleTapUp(e)
//// }
//
// override fun onFling(
// e1: MotionEvent?,
// e2: MotionEvent,
// velocityX: Float,
// velocityY: Float
// ): Boolean {
//Blog.LOGE("e1.pointerCount >> ${e1?.pointerCount}")
// Blog.LOGE("e2.pointerCount >> ${e2.pointerCount}")
// val distanceX = e2.x - (e1?.x ?: 0f)
// val distanceY = e2.y - (e1?.y ?: 0f)
// if (Math.abs(distanceX) > Math.abs(distanceY)
// && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD
// && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
// if (distanceX > 0) onSwipeRight() else onSwipeLeft()
// return true
// } else if (Math.abs(distanceY) > Math.abs(distanceX)
// && Math.abs(distanceY) > SWIPE_DISTANCE_THRESHOLD
// && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
// if (distanceY > 0) onSwipeDown() else onSwipeUp()
// return true
// } else {
//
// }
// return false
// }
// }
// init {
// gestureDetector = GestureDetector(context, GestureListener())
// }
//}
@@ -0,0 +1,5 @@
package com.mime.dualscreenview.view
interface PagedTextGenerateInterface {
fun completePagination(pageList: ArrayList<CharSequence>)
}
@@ -0,0 +1,314 @@
package com.mime.dualscreenview.view
import android.annotation.TargetApi
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.os.Handler
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Guideline
import androidx.core.view.doOnLayout
import androidx.core.view.updateLayoutParams
import com.mime.dualscreenview.R
import com.mime.dualscreenview.common.Blog
class PagedTextLayout : ConstraintLayout , PagedTextGenerateInterface {
constructor(context: Context) : super(context) {initView(context)}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {initView(context)}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {initView(context)}
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {initView(context)}
var mainTextView : TextView? = null
var sencondTextView : TextView? = null
var demp : View? = null
var hiddenTextView : PagedTextView? = null
var guideLine : Guideline? = null
var pageList: ArrayList<CharSequence>? = null
var text : String = ""
set(new) {
Blog.LOGE("field >> ${field}")
Blog.LOGE("new >> ${new}")
field = new
Blog.LOGE("field >> ${field}")
hiddenTextView?.setTxtF(field)
hiddenTextView?.visibility = VISIBLE
}
private val hanler = Handler()
var mPagedTextViewInterface : PagedTextViewInterface? = null
val touchTimeover = Runnable {
mPagedTextViewInterface?.onTimeoverTouch()
}
var currentPageTextView : TextView? = null
fun initView(context: Context) {
inflate(context, R.layout.layout_textviewer, this)
mainTextView = findViewById(R.id.first_view)
sencondTextView = findViewById(R.id.sencond_view)
demp = findViewById(R.id.demp)
currentPageTextView = findViewById(R.id.current_page)
hiddenTextView = findViewById(R.id.hidden_view)
hiddenTextView?.mPagedTextGenerateInterface = this
currentPageTextView?.text = ""
hanler.removeCallbacks(touchTimeover)
setOnLongClickListener { v ->
mPagedTextViewInterface?.onLongClick()
return@setOnLongClickListener false
}
setOnTouchListener(SimpleFingerGestures(omfgl = object : SimpleFingerGestures.OnFingerGestureListener{
override fun onSwipeUp(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
mPagedTextViewInterface?.onSwipeUp(fingers)
return false
}
override fun onSwipeDown(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
mPagedTextViewInterface?.onSwipeDown(fingers)
return false
}
override fun onSwipeLeft(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
mPagedTextViewInterface?.onSwipeLeft(fingers)
return false
}
override fun onSwipeRight(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
mPagedTextViewInterface?.onSwipeRight(fingers)
return false
}
override fun onPinch(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
return false
}
override fun onUnpinch(
fingers: Int,
gestureDuration: Long,
gestureDistance: Double
): Boolean {
mPagedTextViewInterface?.onLongClick()
return false
}
override fun onDoubleTap(fingers: Int): Boolean {
hanler.removeCallbacks(touchTimeover)
mPagedTextViewInterface?.onTouch(TouchArea.Center)
hanler?.postDelayed(touchTimeover, 3000L)
return false
}
}))
// {
// override fun onSwipeUp() {
// mPagedTextViewInterface?.onLongClick()
// }
//
// override fun onSwipeDown() {
// mPagedTextViewInterface?.onLongClick()
// }
// override fun onSwipeLeft() {
// mPagedTextViewInterface?.onSwipeLeft()
// }
//
// override fun onSwipeRight() {
// mPagedTextViewInterface?.onSwipeRight()
// }
//
// override fun onSingleTap(touchArea: TouchArea) {
// if(TouchArea.Center.equals(touchArea)) {
// hanler.removeCallbacks(touchTimeover)
// mPagedTextViewInterface?.onTouch(touchArea)
// hanler?.postDelayed(touchTimeover, 3000L)
// } else {
// mPagedTextViewInterface?.onTouch(touchArea)
// }
// }
//
// override fun onDoubleTap(area: TouchArea) {
// if(TouchArea.Center.equals(area)) {
//// hanler.removeCallbacks(touchTimeover)
//// mPagedTextViewInterface?.onTouch(area)
//// hanler?.postDelayed(touchTimeover, 3000L)
// } else {
// mPagedTextViewInterface?.onTouch(area)
// }
// }
// })
}
fun layoutChange(needDualPage: Boolean) {
Blog.LOGD(log = "layoutChange>> ${this::class.java.name}")
if (needDualPage) {
sencondTextView?.visibility = VISIBLE
demp?.visibility = VISIBLE
} else {
sencondTextView?.visibility = GONE
demp?.visibility = GONE
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
Blog.LOGD(log = "onLayout>> ${this::class.java.name} changed >> ${changed}")
if(changed) {
hiddenTextView?.text = text
forceUpdateUI()
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Blog.LOGD(log = "onSizeChanged>> ${this::class.java.name}")
if (w != oldw || oldh != h) {
postDelayed(Runnable {
layoutChange(w > (h * 0.7f))
},20)
}else {
}
}
var currentPage = 0
override fun completePagination(pageList: ArrayList<CharSequence>) {
Blog.LOGD(log = "completePagination>> ${this::class.java.name} >> pageList ${pageList}")
if(text.length > 0 && pageList!= null && pageList.size == 0) {
} else {
this.pageList = pageList
setPageBy(0)
}
hiddenTextView?.visibility = GONE
}
fun setColorStyle(colors : Array<String>) {
setBackgroundColor(Color.parseColor(colors.get(1)))
mainTextView?.setBackgroundColor(Color.parseColor(colors.get(1)))
sencondTextView?.setBackgroundColor(Color.parseColor(colors.get(1)))
mainTextView?.setTextColor(Color.parseColor(colors.get(0)))
sencondTextView?.setTextColor(Color.parseColor(colors.get(0)))
}
// fun setPagedTextViewInterface(pagedTextViewInterface: PagedTextViewInterface) = hiddenTextView?.setPagedTextViewInterface(pagedTextViewInterface)
// fun setText(replace: String) = hiddenTextView?.setText(replace)
fun setTextSize(fl: Float) {
hiddenTextView?.setTextSize(fl)
mainTextView?.setTextSize(fl)
sencondTextView?.setTextSize(fl)
}
fun next(page : Int) {
}
fun isDualPage() : Boolean {
return sencondTextView?.visibility == VISIBLE
}
fun setPageBy(num : Int) {
currentPage = num
var realPage = if(isDualPage()) currentPage * 2 else currentPage
Blog.LOGE("realPage = if(${pageList?.size} ?: 0 > ${realPage}) { realPage} else { ${(pageList?.size ?: 0) - 1 }}")
realPage = if(pageList?.size ?: 0 > realPage) { realPage} else { (pageList?.size ?: 0) - 1 }
currentPageTextView?.text = "${realPage + 1 }/${ pageList?.size ?: 0 + 1}"
mainTextView?.text = pageList?.get(realPage) ?: "NONE"
if(isDualPage()) {
realPage = realPage.inc()
sencondTextView?.text = if(pageList?.size ?: 0 > realPage) { pageList?.get(realPage)} else { ""}
} else {
sencondTextView?.text = ""
}
}
fun size(): Int = if(isDualPage()) Math.round((hiddenTextView?.size() ?:0) * 0.5f) else hiddenTextView?.size() ?: 0
fun getFastPageCount() = if(isDualPage()) 3 else 6
fun current(): Int = currentPage
fun doNext(fast : Boolean = false) {
if (fast) {
setPageBy(if((currentPage + getFastPageCount()) >= 0) {
currentPage + getFastPageCount()
} else {size()})
} else {
setPageBy(currentPage.inc())
}
}
fun doPrev(fast : Boolean = false) {
if (fast) {
setPageBy(if((currentPage - getFastPageCount()) >= 0) {
currentPage - getFastPageCount()
} else {0})
} else {
setPageBy(if(currentPage > 0 )currentPage.dec() else 0)
}
}
fun forceUpdateUI() {
// mPagedTextViewInterface?.onTouch(TouchArea.Center)
hiddenTextView?.doUpdate()
// hanler?.postDelayed(touchTimeover, 3000L)
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
hiddenTextView?.setPadding(left,top,right, bottom)
mainTextView?.setPadding(left,top,right, bottom)
sencondTextView?.setPadding(left,top,right, bottom)
}
fun setTypeface(tf: Typeface?) {
hiddenTextView?.setTypeface(tf)
mainTextView?.setTypeface(tf)
sencondTextView?.setTypeface(tf)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun setLetterSpacing(letterSpacing: Float) {
hiddenTextView?.letterSpacing = letterSpacing.times(0.01f)
mainTextView?.letterSpacing = letterSpacing.times(0.01f)
sencondTextView?.letterSpacing = letterSpacing.times(0.01f)
}
fun setLineSpacing(mult: Float) {
hiddenTextView?.setLineSpacing(1f, 1f.plus(mult.times(0.01f)))
mainTextView?.setLineSpacing(1f, 1f.plus(mult.times(0.01f)))
sencondTextView?.setLineSpacing(1f, 1f.plus(mult.times(0.01f)))
}
}
@@ -0,0 +1,254 @@
package com.mime.dualscreenview.view
import android.annotation.TargetApi
import android.content.Context
import android.graphics.Typeface
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import com.mime.dualscreenview.common.Blog
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlin.math.min
class PagedTextView : AppCompatTextView {
private var needPaginate = false
private var isPaginating = false
private val pageList = arrayListOf<CharSequence>()
private var pageIndex: Int = 0
private var pageHeight: Int = 0
private var originalText: CharSequence = ""
var mPagedTextGenerateInterface : PagedTextGenerateInterface? = null
constructor(context: Context?) : super(context!!){initView(context)}
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs){initView(context)}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context!!, attrs, defStyleAttr){initView(context)}
fun initView(context: Context?){
}
fun size(): Int = pageList.size
fun current() : Int = pageIndex
fun doPrev() {
if (pageIndex > 0 )
pageIndex = pageIndex - 1
setPageText()
}
fun doNext() {
if (pageIndex < pageList.size)
pageIndex = pageIndex + 1
setPageText()
}
fun next(index: Int) {
pageIndex = index
setPageText()
}
private fun setPageText() {
if(pageList.size > 0) {
isPaginating = true
text = pageList[pageIndex]
isPaginating = false
}
}
fun setTxtF(text: CharSequence?) {
needPaginate = true
this.setText(text , null)
}
override fun setText(text: CharSequence?, type: BufferType?) {
if (!isPaginating) {
needPaginate = true
originalText = text ?: ""
}
super.setText(text, type)
}
override fun setTextSize(unit: Int, size: Float) {
super.setTextSize(unit, size)
needPaginate = true
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, bottom)
needPaginate = true
}
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
super.setPaddingRelative(start, top, end, bottom)
needPaginate = true
}
override fun setTextScaleX(size: Float) {
if (size != textScaleX) {
needPaginate = true
}
super.setTextScaleX(size)
}
override fun setTypeface(tf: Typeface?) {
if (typeface != null && tf != typeface) {
needPaginate = true
}
super.setTypeface(tf)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun setLetterSpacing(letterSpacing: Float) {
if (letterSpacing != this.letterSpacing) {
needPaginate = true
}
super.setLetterSpacing(letterSpacing)
}
override fun setHorizontallyScrolling(whether: Boolean) {
super.setHorizontallyScrolling(false)
}
override fun setLineSpacing(add: Float, mult: Float) {
if (add != lineSpacingExtra || mult != lineSpacingMultiplier) {
needPaginate = true
}
super.setLineSpacing(add, mult)
}
override fun setMaxLines(maxLines: Int) {
if (maxLines != this.maxLines) {
needPaginate = true
}
super.setMaxLines(maxLines)
}
override fun setLines(lines: Int) {
super.setLines(lines)
if (lines != this.lineCount) {
needPaginate = true
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Blog.LOGD(log = "onSizeChanged>> ${this::class.java.name}")
pageHeight = ((h - (marginTop + marginBottom + paddingTop + paddingBottom)) * 0.8f).toInt()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
Blog.LOGD(log = "onLayout>> ${this::class.java.name} changed >> ${changed}")
if (changed || needPaginate) {
paginate()
setPageText()
needPaginate = false
}
}
fun doUpdate() {
if (needPaginate && layout != null) {
paginate()
setPageText()
needPaginate = false
}
}
private fun paginate() {
if (layout != null) {
MainScope().launch {
pageList.clear()
Blog.LOGD(log = "paginate>> ${this::class.java.name} && ${layout.text}")
val layout = from(layout)
val lines = if(min(maxLines, layout.lineCount) > 10) {min(maxLines, layout.lineCount) - 1} else {min(maxLines, layout.lineCount)}
var startOffset = 0
val heightWithoutPaddings = pageHeight - (marginTop + marginBottom + paddingTop + paddingBottom)
var height = heightWithoutPaddings
for (i in 0 until lines) {
if (height < layout.getLineBottom(i)) {
pageList.add(
layout.text.subSequence(startOffset, layout.getLineStart(i))
)
startOffset = layout.getLineStart(i)
height = layout.getLineTop(i) + heightWithoutPaddings
}
if (i == lines - 1) {
pageList.add(
if(layout.lineCount > i) {
layout.text.subSequence(startOffset, layout.getLineEnd(i + 1))
} else {
layout.text.subSequence(startOffset, layout.getLineEnd(i))
}
)
}
}
mPagedTextGenerateInterface?.completePagination(pageList)
}
}
}
private fun from(layout: Layout): Layout =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@Suppress("DEPRECATION")
(StaticLayout(
originalText,
paint,
layout.width,
layout.alignment,
lineSpacingMultiplier,
lineSpacingExtra,
includeFontPadding
))
} else {
StaticLayout.Builder
.obtain(originalText, 0, originalText.length, paint, ((layout.width - (paddingLeft + paddingRight + marginLeft + marginRight) * 0.85f)).toInt())
.setAlignment(layout.alignment)
.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
.setIncludePad(includeFontPadding)
.setUseLineSpacingFromFallbacks()
.setBreakStrategy(breakStrategy)
.setHyphenationFrequency(hyphenationFrequency)
.setJustificationMode()
.setMaxLines(maxLines)
.build()
}
private fun StaticLayout.Builder.setUseLineSpacingFromFallbacks(): StaticLayout.Builder {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
this.setUseLineSpacingFromFallbacks(isFallbackLineSpacing)
}
return this
}
private fun StaticLayout.Builder.setJustificationMode(): StaticLayout.Builder {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.setJustificationMode(justificationMode)
}
return this
}
}
@@ -0,0 +1,11 @@
package com.mime.dualscreenview.view
interface PagedTextViewInterface {
fun onTouch(touchArea: TouchArea)
fun onTimeoverTouch()
fun onSwipeLeft(touchCount : Int)
fun onSwipeRight(touchCount : Int)
fun onSwipeDown(touchCount : Int)
fun onSwipeUp(touchCount : Int)
fun onLongClick()
}
@@ -0,0 +1,64 @@
package com.mime.dualscreenview.view
import android.content.Context
import android.util.AttributeSet
import android.widget.Button
import androidx.appcompat.widget.AppCompatButton
import com.mime.dualscreenview.R
class ScopeEditor: SideButtonTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
var titleArray : Array<String>? = null
set(value) {
if(value != null && value.size < 1) {
Error("titleArray는 최소 한개 이상이여야됨.")
return
}
field = value
maxValue = field!!.size
}
var maxValue : Int = 1
set(value) {
if (value < 1) {
Error("maxValue는 무조건 0보다 커야하눈뎅....")
return
}
field = value
}
override var value : Int = 14
set(newValue) {
field = newValue
text_value?.text = displayFormat.format(titleArray?.get(field) ?:"defulat")
mValueChange?.invoke(field)
}
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
// super.i
// inflate(context, R.layout.layout_steps_editor,this)
btn_decrement?.setOnClickListener { value = Math.abs(value.dec()).rem(maxValue) }
btn_increment?.setOnClickListener { value = value.inc().rem(maxValue) }
leftButtonTitle = "<"
rightButtonTitle = ">"
}
}
@@ -0,0 +1,78 @@
package com.mime.dualscreenview.view
import android.content.Context
import android.util.AttributeSet
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.constraintlayout.widget.ConstraintLayout
import com.mime.dualscreenview.R
import java.lang.Exception
open class SideButtonTextView : ConstraintLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
var leftButtonTitle : String = ""
set(value) {
field = value
btn_decrement?.text = field
}
var rightButtonTitle : String = ""
set(value) {
field = value
btn_increment?.text = field
}
open var value : Int = 14
set(newValue) {
field = newValue
text_value?.text = displayFormat.format(field)
mValueChange?.invoke(field)
}
var displayFormat = "글자 크기 : %d"
set(newValue) {
field = newValue
try {
text_value?.text = displayFormat.format(value)
} catch (e : Exception) {
}
}
var mValueChange : ValueChange? = null
set(newValue) {
field = newValue
if (newValue != null) {
newValue(value)
}
}
var btn_decrement : AppCompatButton? = null
var btn_increment : AppCompatButton? = null
var text_value : TextView? = null
init {
inflate(context, R.layout.layout_steps_editor,this)
btn_increment = findViewById(R.id.btn_increment)
btn_decrement = findViewById(R.id.btn_decrement)
text_value = findViewById(R.id.text_value)
btn_decrement?.setOnClickListener { value = value.dec() }
btn_increment?.setOnClickListener { value = value.inc() }
}
}
@@ -0,0 +1,32 @@
package com.mime.dualscreenview.view
import android.content.Context
import android.util.AttributeSet
import com.mime.dualscreenview.R
typealias ValueChange = (Int)->Unit
class StepsEditor : SideButtonTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
// inflate(context, R.layout.layout_steps_editor,this)
btn_decrement?.setOnClickListener { value = value.dec() }
btn_increment?.setOnClickListener { value = value.inc() }
leftButtonTitle = "-"
rightButtonTitle = "+"
}
}
@@ -0,0 +1,81 @@
package com.mime.dualscreenview.webcontents
import android.util.Log
import android.webkit.*
import com.mime.dualscreenview.webcontents.contentsinfo.ActionByBool
import com.mime.dualscreenview.webcontents.contentsinfo.ContentsInfoInterface
import com.mime.dualscreenview.webcontents.contentsinfo.DidFindContents
abstract class BaseWebContents : ContentsInfoInterface {
open var lastNumber : Int = 221
var completeAction : ActionByBool? = null
val definedActionCount = 5
var completeActionCount = 0
set(value) {
field = value
if(field == definedActionCount && completeAction != null) {
completeAction?.invoke(true)
}
}
fun findListItem(webview: WebView, callBakItems : DidFindContents) {
webview.evaluateJavascript(getContentsList()) { result ->
callBakItems.invoke(result)
}
}
fun doOnloaded(webview: WebView, findContents : DidFindContents, findTitle : DidFindContents, findNextButton : DidFindContents,
findPrevButton : DidFindContents, completeAction : ActionByBool) {
completeActionCount = 0
this.completeAction = completeAction
webview.evaluateJavascript(getTitleJs()) { result : String? ->
result?.let { resultString ->
findTitle.invoke(resultString)
}
completeActionCount = completeActionCount + 1
}
webview.evaluateJavascript(getFindContentsJs()) { result : String? ->
result?.let { resultString ->
checkCorrectContents(resultString)?.let { contents ->
if(resultString != null && !"null".equals(resultString) && !resultString.isNullOrEmpty()) {
findContents.invoke(contents)
} else {
findContents.invoke(null)
}
}
} ?: findContents.invoke(null)
completeActionCount = completeActionCount + 1
}
webview.evaluateJavascript("document.getElementById('${getNextButtonJs()}')") { result : String? ->
result?.let { resultString ->
Log.e("BaseWebContents", "getNextButtonJs() >> ${resultString}")
if(resultString != null && !"null".equals(resultString)) {
findNextButton.invoke(resultString)
} else {
findNextButton.invoke(null)
}
}
completeActionCount = completeActionCount + 1
}
webview.evaluateJavascript("document.getElementById('${getPrevButtonJs()}')") { result : String? ->
result?.let { resultString ->
Log.e("BaseWebContents", "getPrevButtonJs() >> ${resultString}")
if(resultString != null && !"null".equals(resultString)) {
findPrevButton.invoke(resultString)
}else {
findPrevButton.invoke(null)
}
}
completeActionCount = completeActionCount + 1
}
webview.evaluateJavascript(onLoadedJs()) {
Log.e("BaseWebContents", "onLoadedJs() >> ${it}")
completeActionCount = completeActionCount + 1
}
}
}
@@ -0,0 +1,228 @@
package com.mime.dualscreenview.webcontents
import android.app.AlertDialog
import android.graphics.Bitmap
import android.net.Uri
import android.net.http.SslError
import android.util.Log
import android.webkit.*
import com.mime.dualscreenview.common.Blog
import com.mime.dualscreenview.data.HistoryManager
import com.mime.dualscreenview.data.model.LastInfo
import com.mime.dualscreenview.webcontents.contentsinfo.Booktoki
import com.mime.dualscreenview.webcontents.contentsinfo.DidFindContents
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.JSONObject
open class BaseWebContentsViewer {
var currentContentsProvider : BaseWebContents? = null
lateinit var webview : WebView
lateinit var mainControllInterface : MainControllInterface
@JavascriptInterface
fun onBookInfo(jsonData : String) {
GlobalScope.launch {
try {
Blog.LOGE("BaseWebContentsViewer",jsonData)
val data: JSONObject = JSONObject(jsonData)
Blog.LOGE("BaseWebContentsViewer",data.toString())
mainControllInterface?.onBookInfos(jsonData)
} catch (e : Exception) {
}
}
}
constructor(webview : WebView, mainControllInterface : MainControllInterface ) {
this.webview = webview
this.mainControllInterface = mainControllInterface
webview.webChromeClient = rootWebChromeClient
webview.webViewClient = rootWebViewClient
webview.settings.textZoom = 100
webview.addJavascriptInterface(this,"PAgit")
webview.settings.javaScriptEnabled = true
webview.settings.javaScriptCanOpenWindowsAutomatically = false
webview.settings.loadWithOverviewMode = true
webview.settings.setPluginState(WebSettings.PluginState.ON)
webview.settings.domStorageEnabled = true
webview.clearCache(true);
webview.clearHistory();
webview.clearSslPreferences();
WebView.setWebContentsDebuggingEnabled(true)
}
constructor()
fun loadContents(webContents: BaseWebContents) {
currentContentsProvider = webContents
currentContentsProvider?.let {
webview.loadUrl(it.getLastedDoamin())
}
}
val rootWebChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
}
}
fun findListItem(callBakItems : DidFindContents) {
currentContentsProvider?.findListItem(webview,callBakItems)
}
fun loadLastInfo(lastInfo: LastInfo) {
lastInfo?.let { last ->
Blog.LOGE("last.pageUrl >>> ${last.pageUrl}")
currentContentsProvider = WebContentsManger.getBaseWebContentsBy(last.contentsName)
// HistoryManager.getBooPageInfo(last.pageUrl,{ page ->
// if(page != null){
// if(page.contents?.length ?: 0 > 0) {
// mainControllInterface?.onLoadedContents(page.contents!!)
// } else {
// webview.loadUrl(last.pageUrl)
// }
// } else {
if (last.pageUrl.startsWith("https://")) {
webview.loadUrl(last.pageUrl)
} else {
try {
var uri = Uri.parse(webview.url)
uri.path?.let {
webview.loadUrl(uri.toString().replace(it,last.pageUrl))
}
} catch (e : Exception) {
webview.loadUrl(Booktoki.getLastedDoamin().plus("/").plus(last.pageUrl))
}
}
// }
// })
}
}
val rootWebViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
Log.e("shouldInterceptRequest", " >>>> ${request?.url?.toString()} , ${request?.url?.toString()?.contains("gif")}")
if(request?.url?.toString()?.contains("gif") ?: false) {
return WebResourceResponse("text/javascript", "UTF-8", null);
}
// if(request?.url?.toString()?.contains(currentContentsProvider?.acccceptResourceKeyword() ?: "") == false && request?.url?.toString()?.contains(currentContentsProvider?.getLastedDoamin() ?: "") == false) {
// return WebResourceResponse("text/javascript", "UTF-8", null);
// }
return super.shouldInterceptRequest(view, request)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
mainControllInterface?.onStartLoad()
}
var alertDialogs : ArrayList<AlertDialog> = arrayListOf()
// override fun onReceivedError(
// view: WebView?,
// request: WebResourceRequest?,
// error: WebResourceError?
// ) {
// super.onReceivedError(view, request, error)
// if (error != null && error.errorCode < 0) {
//
// webview.postDelayed({
// val builder = AlertDialog.Builder(webview.context)
//
//
// builder.setTitle("로딩 에러").setMessage("${currentContentsProvider?.getWebcontentsName()} ${currentContentsProvider?.lastNumber?.inc()} 여기로 다시 시도?!!")
// builder.setPositiveButton(
// "오키 ${currentContentsProvider?.lastNumber?.inc()} 레고!"
// ) { dialog, id ->
// dialog.dismiss()
// currentContentsProvider?.lastNumber = currentContentsProvider?.lastNumber!!.inc()
// loadContents(currentContentsProvider!!)
// }
//
// builder.setNegativeButton(
// "포기하자"
// ) { dialog, id ->
// dialog.dismiss()
// }
// builder.setNeutralButton(
// "같은 주소로 다시 도전"
// ) { dialog, id ->
// loadContents(currentContentsProvider!!)
// dialog.dismiss()
// }
// alertDialogs.add(builder.create())
//
// alertDialogs.last()?.show()
//
// },500)
// }
//
// }
//
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
// super.onReceivedSslError(view, handler, error)
Blog.LOGE(log= "onReceivedSslError >> ${error}")
handler?.proceed()
}
override fun onReceivedHttpError(
view: WebView?,
request: WebResourceRequest?,
errorResponse: WebResourceResponse?
) {
Blog.LOGE(log= "onReceivedHttpError >> ${errorResponse}")
// super.onReceivedHttpError(view, request, errorResponse)
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.let {
if(url?.contains("/list/") ?: false && url?.contains("agit") ?: false){
findListItem {
Blog.LOGE("onPageFinished", url ?: "")
}
} else if (url?.contains("booktoki") ?: false){
findListItem {
Blog.LOGE("onPageFinished", url ?: "")
}
}
currentContentsProvider?.doOnloaded(it , { result ->
result?.let { mainControllInterface.onLoadedContents(it) }
} , {
it?.let { mainControllInterface.onFindTitle(it.replace("\"","")) }
}, {btn ->
mainControllInterface?.showNextBtn (btn != null){
webview?.evaluateJavascript("if(document.getElementById('${currentContentsProvider!!.getNextButtonJs()}') != null) document.getElementById('${currentContentsProvider!!.getNextButtonJs()}').click()") {
}
}
},{btn ->
mainControllInterface?.showPrevBtn(btn != null) {
webview?.evaluateJavascript("if(document.getElementById('${currentContentsProvider!!.getPrevButtonJs()}') != null) document.getElementById('${currentContentsProvider!!.getPrevButtonJs()}').click()") {
}
}
}, { complete ->
if(complete) {
mainControllInterface?.completePageLoad(LastInfo().apply {
this.pageUrl = url ?: currentContentsProvider?.getLastedDoamin() ?: ""
this.contentsName = currentContentsProvider?.getWebcontentsName() ?: ""
this.pageIndex = 0
})
}
})
}
}
}
}
@@ -0,0 +1,5 @@
//package com.mime.dualscreenview.webcontents
//
//typealias ActionByBool = (Boolean) -> Unit
//typealias DidFindContents = (String?) -> Unit
//typealias GotoSomeWhere = () -> Unit
@@ -0,0 +1,20 @@
package com.mime.dualscreenview.webcontents
import com.mime.dualscreenview.data.model.LastInfo
import com.mime.dualscreenview.webcontents.contentsinfo.GotoSomeWhere
interface MainControllInterface {
fun onStartLoad()
fun completePageLoad(apply: LastInfo)
fun showNextBtn(finnd : Boolean, onClickAction: GotoSomeWhere)
fun showPrevBtn(finnd : Boolean,onClickAction: GotoSomeWhere)
fun showAlert(alert :String)
fun onLoadedContents(contents :String)
fun onFindTitle(contents :String)
fun onBookInfos(jsonString : String)
}
@@ -0,0 +1,19 @@
package com.mime.dualscreenview.webcontents
import com.mime.dualscreenview.webcontents.contentsinfo.Agit
import com.mime.dualscreenview.webcontents.contentsinfo.Booktoki
object WebContentsManger {
val allContentsList : ArrayList<BaseWebContents> = arrayListOf(Booktoki)
fun getBaseWebContentsBy(name : String) : BaseWebContents {
var correctContents : BaseWebContents = Booktoki
for (contents in allContentsList) {
if(name.equals(contents.getWebcontentsName())) {
correctContents = contents
break
}
}
return correctContents
}
}
@@ -0,0 +1,138 @@
package com.mime.dualscreenview.webcontents.contentsinfo
import com.mime.dualscreenview.webcontents.BaseWebContents
object Agit : BaseWebContents() {
override var lastNumber : Int = 664
override fun getWebcontentsName(): String {
return "Agit"
}
override fun getLastedDoamin(): String {
return String.format("https://agit%d.xyz/", lastNumber)
}
override fun getContentsList(): String {
return " var datasets = [];\n" +
" var pageNum = 0;\n" +
" var totalCount = 0;\n" +
" var currentIdx = 0;\n" +
" function getContentList() {\n" +
" \n" +
" if(document.getElementById('count_list') != undefined && totalCount <= 0) {\n" +
" totalCount = Number(document.getElementById('count_list').textContent);\n" +
" console.log(\"totalCount \" + totalCount);\n" +
" currentIdx = totalCount;\n" +
" }\n" +
" \n" +
" var bookTitle = document.getElementsByClassName('pt-2')[1].textContent;\n" +
" var bookPageUrl = new URL(location.href).pathname;\n" +
" var pages = document.getElementById('id_page_novel_c').childNodes;\n" +
" var list = document.getElementById('id_list_novel_c').childNodes;\n" +
" console.log(\"list.length >> \" + list.length);\n" +
" for (i = 0; i < list.length; i++) {\n" +
" var onclickValue = list[i].attributes['onclick'].value;\n" +
" var pageUrl = onclickValue.split('location.href=')[1].replace(';', '');\n" +
" var chapterTitle = list[i].childNodes[1].children[0].textContent;\n" +
" var chapterID = list[i].childNodes[1].getAttribute('data-wr-id-c');\n" +
" \n" +
" var data = {\n" +
" 'chapterID': Number(chapterID),\n" +
" 'idx': Number(currentIdx),\n" +
" 'pathUrl': pageUrl,\n" +
" 'bookPageUrl': bookPageUrl,\n" +
" 'chapterTitle': chapterTitle,\n" +
" 'bookTitle': bookTitle,\n" +
" };\n" +
" datasets.push(data);\n" +
" currentIdx = currentIdx - 1\n" +
" }\n" +
" \n" +
" \n" +
" \n" +
" var currentTime = 500;\n" +
" try { currentTime = new Date().valueOf() % 1000;\n" +
" \n" +
" } catch (e) {\n" +
" \n" +
" }\n" +
" \n" +
" console.log(\"pages.length >> \" + pages.length);\n" +
" console.log(\"datasets.length >> \" + datasets.length);\n" +
" console.log(\"totalCount >> \" + totalCount);\n" +
" console.log(\"pages.length >> \" + pages.length);\n" +
" \n" +
" if ((pages.length > pageNum) ||\n" +
" (totalCount > datasets.length)) {\n" +
" pageNum += 1;\n" +
" get_data_novel_list_c(pageNum);\n" +
" setTimeout(function() {\n" +
" getContentList();\n" +
" }, 2000 + currentTime);\n" +
" } else if ((pages.length == pageNum) ||\n" +
" (totalCount > 10 &&\n" +
" totalCount == datasets.length)) {\n" +
" datasets.sort((a, b) => b.idx < a.idx);\n" +
" var i = 0;\n" +
" datasets.forEach(function(item) {\n" +
" item['chapterNum'] = i;\n" +
" i = i + 1;\n" +
" } );\n" +
" \n" +
" PAgit.onBookInfo(JSON.stringify({\n" +
" 'bookTitle': bookTitle,\n" +
" 'bookPageUrl': new URL(location.href)\n" +
" .pathname,\n" +
" 'pages': datasets ,\n" +
" }));\n" +
" }\n" +
" }\n" +
" \n" +
" function wait(sec) {\n" +
" let start = Date.now(),\n" +
" now = start;\n" +
" while (now - start < sec * 1000) {\n" +
" now = Date.now();\n" +
" }\n" +
" }\n" +
" getContentList();"
}
override fun acccceptResourceKeyword(): String {
return "toki"
}
override fun getNextButtonJs(): String {
return "goNextBtn"
}
override fun getPrevButtonJs(): String {
return "goPrevBtn"
}
override fun getTitleJs(): String {
return "document.getElementsByClassName(\"toon-title\").length > 0 ? document.getElementsByClassName(\"toon-title\")[0].title : null"
}
override fun getFindContentsJs(): String {
return "document.getElementById('id_wr_content') != null ? document.getElementById('id_wr_content').innerText : null"
}
override fun checkCorrectContents(contents: String): String {
return if (contents != null && !contents.isNullOrEmpty()) {
contents
} else {
"fail load"
}
}
override fun onLoadedJs(): String {
return "if(document.getElementsByClassName(\"hd_pops\") != null && document.getElementsByClassName(\"hd_pops\").length > 0) document.getElementsByClassName(\"hd_pops\")[0].remove();" +
"if(document.getElementsByClassName(\"hd_pops\") != null && document.getElementsByClassName(\"hd_pops\").length > 0) document.getElementsByClassName(\"hd_pops\")[0].remove();" +
"if(document.getElementById(\"main-banner-view\") != null) document.getElementById(\"main-banner-view\").remove();" +
"if(document.getElementsByClassName(\"board-tail-banner\") != null && document.getElementsByClassName(\"board-tail-banner\").length > 0)document.getElementsByClassName(\"board-tail-banner\")[0].remove();" +
"if(document.getElementById(\"id_mbv\") != null)document.getElementById(\"id_mbv\").remove();"
}
}
@@ -0,0 +1,95 @@
package com.mime.dualscreenview.webcontents.contentsinfo
import com.mime.dualscreenview.webcontents.BaseWebContents
object Booktoki : BaseWebContents() {
override var lastNumber : Int = 350
override fun getWebcontentsName(): String {
return "Booktoki"
}
override fun getLastedDoamin(): String {
return String.format("https://booktoki%d.com/", lastNumber)
}
override fun getContentsList(): String {
return "function getList() {\n" +
" const contentsArray = [];\n" +
" var children = document.getElementsByClassName('list-body')[0].children;\n" +
" var maxCount = children.length;\n" +
" for (i= 0; i < maxCount; i++) {\n" +
" var chapterNum = children[i].getElementsByClassName('wr-num')[0].textContent;\n" +
" var pageUrl = children[i].getElementsByClassName('wr-subject')[0].getElementsByTagName('a')[0].href;\n" +
" if (pageUrl != null && pageUrl.length > 0 && pageUrl.startsWith(\"http\")) {\n" +
" pageUrl = new URL(pageUrl).pathname;\n" +
" }\n" +
" var chapterTitle = children[i].getElementsByClassName('wr-subject')[0].getElementsByTagName('a')[0].innerText;\n" +
" if(chapterTitle.split('\\n').length > 1) {\n" +
" chapterTitle = chapterTitle.split('\\n')[1];\n" +
" }\n" +
"\n" +
" var bookPageUrl = location.pathname;\n" +
" var bookTitle = document.getElementsByClassName('view-title')[0].getElementsByTagName('span')[0].innerText;\n" +
" var data = {\n" +
" 'chapterID': Number(chapterNum),\n" +
" 'chapterNum': Number(chapterNum),\n" +
" 'pathUrl': pageUrl,\n" +
" 'bookPageUrl': bookPageUrl,\n" +
" 'chapterTitle': chapterTitle,\n" +
" 'bookTitle': bookTitle,\n" +
" };\n" +
" contentsArray.push(\n" +
" data\n" +
" );\n" +
" }\n" +
" \n" +
" if(contentsArray.length > 0) {\n" +
" PAgit.onBookInfo(JSON.stringify({\n" +
" 'bookTitle': bookTitle,\n" +
" 'bookPageUrl': new URL(location.href).pathname,\n" +
" 'pages': contentsArray ,\n" +
" }));\n" +
" }\n" +
"}\n" +
"getList()\n" +
""
}
override fun acccceptResourceKeyword(): String {
return "toki"
}
override fun getNextButtonJs(): String {
return "goNextBtn"
}
override fun getPrevButtonJs(): String {
return "goPrevBtn"
}
override fun getTitleJs(): String {
return "document.getElementsByClassName(\"toon-title\").length > 0 ? document.getElementsByClassName(\"toon-title\")[0].title : null"
}
override fun getFindContentsJs(): String {
return "document.getElementById(\"novel_content\") != null ? document.getElementById(\"novel_content\").innerText : null"
}
override fun checkCorrectContents(contents: String): String {
return if (contents != null && !contents.isNullOrEmpty()) {
contents
} else {
"fail load"
}
}
// "if(document.getElementsByClassName(\"hd_pops\") != null && document.getElementsByClassName(\"hd_pops\").length > 0) document.getElementsByClassName(\"hd_pops\")[0].remove();" +
// "if(document.getElementsByClassName(\"hd_pops\") != null && document.getElementsByClassName(\"hd_pops\").length > 0) document.getElementsByClassName(\"hd_pops\")[0].remove();" +
// "if(document.getElementById(\"main-banner-view\") != null) document.getElementById(\"main-banner-view\").remove();" +
// "if(document.getElementsByClassName(\"board-tail-banner\") != null && document.getElementsByClassName(\"board-tail-banner\").length > 0)document.getElementsByClassName(\"board-tail-banner\")[0].remove();" +
// "if(document.getElementById(\"id_mbv\") != null)document.getElementById(\"id_mbv\").remove();"
override fun onLoadedJs(): String {
return ""
}
}
@@ -0,0 +1,20 @@
package com.mime.dualscreenview.webcontents.contentsinfo
typealias ActionByBool = (Boolean) -> Unit
typealias DidFindContents = (String?) -> Unit
typealias GotoSomeWhere = () -> Unit
interface ContentsInfoInterface {
fun getWebcontentsName() : String
fun getNextButtonJs() : String
fun getPrevButtonJs() : String
fun getTitleJs() : String
fun getFindContentsJs() : String
fun checkCorrectContents(contents: String) : String
fun getLastedDoamin() : String
fun onLoadedJs() : String
fun acccceptResourceKeyword() : String
fun getContentsList() : String
}
@@ -0,0 +1,18 @@
function getList() {
var children = document.getElementsByClassName('list-body')[0].children
var maxCount = children.length
const contentsArray = []
for (i= 0; i < maxCount; i++) {
children[i].getElementsByClassName('wr-num')
contentsArray.push(
{
'title' : children[i].text ,
'link' : children[i].value
}
)
}
contentsArray.reverse()
return {'list' :contentsArray}
}
getList()
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M17.921,1.505a1.5,1.5 0,0 1,-0.44 1.06L9.809,10.237a2.5,2.5 0,0 0,0 3.536l7.662,7.662a1.5,1.5 0,0 1,-2.121 2.121L7.688,15.9a5.506,5.506 0,0 1,0 -7.779L15.36,0.444a1.5,1.5 0,0 1,2.561 1.061Z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.079,22.5a1.5,1.5 0,0 1,0.44 -1.06l7.672,-7.672a2.5,2.5 0,0 0,0 -3.536L6.529,2.565A1.5,1.5 0,0 1,8.65 0.444l7.662,7.661a5.506,5.506 0,0 1,0 7.779L8.64,23.556A1.5,1.5 0,0 1,6.079 22.5Z"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.849,23.55a2.954,2.954 0,0 0,3.266 -0.644L12,17.053l5.885,5.853a2.956,2.956 0,0 0,2.1 0.881,3.05 3.05,0 0,0 1.17,-0.237A2.953,2.953 0,0 0,23 20.779V5a5.006,5.006 0,0 0,-5 -5H6A5.006,5.006 0,0 0,1 5V20.779A2.953,2.953 0,0 0,2.849 23.55Z"/>
</vector>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape
android:innerRadiusRatio="2.71"
android:thicknessRatio="7.6"
android:shape="ring"
android:useLevel="false"
android:type="sweep">
<solid android:color="#9E9E9E" />
</shape>
</item>
<item android:id="@android:id/progress">
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.71"
android:thicknessRatio="7.6"
android:shape="ring"
android:angle="0"
android:type="sweep">
</shape>
</rotate>
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+15
View File
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M256,319.84c-35.35,0 -64,28.65 -64,64v128h128v-128C320,348.49 291.35,319.84 256,319.84z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M362.67,383.84v128H448c35.35,0 64,-28.65 64,-64V253.26c0,-11.08 -4.3,-21.73 -12.01,-29.7l-181.29,-195.99c-31.99,-34.61 -85.98,-36.74 -120.59,-4.75c-1.64,1.52 -3.23,3.1 -4.75,4.75L12.4,223.5C4.45,231.5 -0,242.31 0,253.58v194.26c0,35.35 28.65,64 64,64h85.33v-128c0.4,-58.17 47.37,-105.68 104.07,-107.04C312.01,275.38 362.22,323.7 362.67,383.84z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M256,319.84c-35.35,0 -64,28.65 -64,64v128h128v-128C320,348.49 291.35,319.84 256,319.84z"/>
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

+15
View File
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M22.713,4.077A2.993,2.993 0,0 0,20.41 3H4.242L4.2,2.649A3,3 0,0 0,1.222 0H1A1,1 0,0 0,1 2h0.222a1,1 0,0 1,0.993 0.883l1.376,11.7A5,5 0,0 0,8.557 19H19a1,1 0,0 0,0 -2H8.557a3,3 0,0 1,-2.82 -2h11.92a5,5 0,0 0,4.921 -4.113l0.785,-4.354A2.994,2.994 0,0 0,22.713 4.077Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,22m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M17,22m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M34.28,384c17.65,30.63 56.78,41.15 87.4,23.5c0.02,-0.01 0.04,-0.02 0.06,-0.04l9.49,-5.48c17.92,15.33 38.52,27.22 60.76,35.07V448c0,35.35 28.65,64 64,64s64,-28.65 64,-64v-10.94c22.24,-7.86 42.84,-19.77 60.76,-35.12l9.54,5.5c30.63,17.67 69.79,7.17 87.47,-23.47c17.67,-30.63 7.17,-69.79 -23.47,-87.47l0,0l-9.47,-5.46c4.26,-23.2 4.26,-46.99 0,-70.19l9.47,-5.46c30.63,-17.67 41.14,-56.83 23.47,-87.47c-17.67,-30.63 -56.83,-41.14 -87.47,-23.47l-9.49,5.48C362.86,94.64 342.25,82.77 320,74.94V64c0,-35.35 -28.65,-64 -64,-64s-64,28.65 -64,64v10.94c-22.24,7.86 -42.84,19.77 -60.76,35.12l-9.54,-5.53C91.07,86.86 51.91,97.37 34.24,128s-7.17,69.79 23.47,87.47l0,0l9.47,5.46c-4.26,23.2 -4.26,46.99 0,70.19l-9.47,5.46C27.16,314.3 16.69,353.38 34.28,384zM256,170.67c47.13,0 85.33,38.21 85.33,85.33S303.13,341.33 256,341.33S170.67,303.13 170.67,256S208.87,170.67 256,170.67z"/>
</vector>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="320dp"
android:layout_gravity="center"
android:layout_height="420dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/style_dialog_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="0dp"
android:gravity="center"
android:layout_height="@dimen/main_top_height"/>
<androidx.recyclerview.widget.RecyclerView
app:layout_constraintTop_toBottomOf="@id/style_dialog_title"
android:id="@+id/stylez_recyclerview"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
+181
View File
@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/intro_bg"
android:layout_height="match_parent"
tools:context=".activity.Intro">
<WebView
android:id="@+id/hidden_web"
android:layout_margin="60dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<WebView
android:id="@+id/menu_web"
android:layout_margin="20dp"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/btn_left"
app:layout_constraintTop_toBottomOf="@id/textview_title" />
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:id="@+id/btn_home"
android:layout_width="wrap_content"
android:layout_height="@dimen/main_top_height"
android:background="@android:color/transparent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/home"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/textview_title"
android:layout_width="0dp"
android:layout_height="@dimen/main_top_height"
android:text="@string/app_name"
android:gravity="center"
android:textSize="24sp"
app:layout_constraintRight_toLeftOf="@id/btn_list"
app:layout_constraintLeft_toRightOf="@id/btn_home"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:background="@android:color/transparent"
android:id="@+id/btn_list"
android:layout_width="wrap_content"
android:layout_height="@dimen/main_top_height"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:src="@drawable/bookmark"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/textview_title"
app:layout_constraintRight_toLeftOf="@+id/btn_history"
app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<!-- <androidx.constraintlayout.utils.widget.ImageFilterButton-->
<!-- android:id="@+id/btn_rotate"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="@dimen/main_top_height"-->
<!-- android:adjustViewBounds="true"-->
<!-- android:scaleType="centerInside"-->
<!-- android:visibility="gone"-->
<!-- android:src="@drawable/rotation"-->
<!-- android:background="#8FFF"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintLeft_toRightOf="@id/btn_list"-->
<!-- app:layout_constraintRight_toLeftOf="@+id/btn_history"-->
<!-- app:layout_constraintHorizontal_chainStyle="spread_inside"-->
<!-- />-->
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:id="@+id/btn_history"
android:layout_width="wrap_content"
android:layout_height="@dimen/main_top_height"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/saved"
android:background="@android:color/transparent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:background="@android:color/transparent"
android:layout_height="@dimen/main_top_height"
android:tag="1"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:text="@string/display_the_second_screen"
android:visibility="visible"
android:src="@drawable/angle_left"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="spread"
/>
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:id="@+id/btn_setting"
android:layout_width="wrap_content"
android:src="@drawable/settings"
android:layout_height="@dimen/main_top_height"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/btn_left"
app:layout_constraintRight_toLeftOf="@+id/btn_right"
app:layout_constraintHorizontal_chainStyle="spread"
/>
<androidx.constraintlayout.utils.widget.ImageFilterButton
android:id="@+id/btn_right"
android:layout_width="wrap_content"
android:layout_height="@dimen/main_top_height"
android:layout_marginStart="16dp"
android:tag="0"
android:background="@android:color/transparent"
android:src="@drawable/angle_right"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_chainStyle="spread" />
<com.mime.dualscreenview.view.PagedTextLayout
android:id="@+id/paged_layer"
android:layout_margin="1dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:elevation="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/btn_setting"
app:layout_constraintTop_toBottomOf="@id/textview_title" />
<ProgressBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
android:indeterminate="false"
android:max="100"
android:progressBackgroundTint="#FBE7C6"
android:progressDrawable="@drawable/circle_progressbar"
android:progressTint="#edbf41"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!--//style="@style/Widget.AppCompat.ProgressBar.Horizontal"-->
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_margin="2.5dp"
android:id="@+id/bg_sample"
android:layout_height="140dp">
<TextView
android:gravity="center"
android:text=""
android:textAlignment="center"
android:id="@+id/textview_sample"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/setting_title"
android:textAlignment="center"
android:gravity="center"
android:text="Setting"
android:fontFamily="@font/jsdongkang_regular"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="@dimen/setting_item_height"/>
<CheckBox
android:id="@+id/custom_style"
app:layout_constraintTop_toTopOf="@+id/setting_title"
app:layout_constraintBottom_toBottomOf="@id/setting_title"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="5dp"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_decrement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:padding="0dp"
android:textSize="20dp"
android:fontFamily="@font/jsdongkang_regular"
android:background="@color/button_color"
android:backgroundTint="@color/button_color_tint"
app:layout_constraintEnd_toStartOf="@+id/text_value"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_value"
android:layout_width="0dp"
android:gravity="center"
android:layout_height="wrap_content"
android:text="14dp"
android:fontFamily="@font/jsdongkang_regular"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_increment"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_decrement" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_increment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/button_color"
android:text="+"
android:padding="0dp"
android:textSize="20dp"
android:fontFamily="@font/jsdongkang_regular"
android:backgroundTint="@color/button_color_tint"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/text_value" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_margin="5dp"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mime.dualscreenview.view.PagedTextView
android:layout_margin="5dp"
android:id="@+id/hidden_view"
android:visibility="visible"
android:layout_width="0dp"
android:layout_weight="1"
android:includeFontPadding="false"
android:lineSpacingExtra="0dp"
style="@style/sss"
android:layout_height="match_parent"/>
<View
android:id="@+id/demp"
android:visibility="gone"
android:layout_margin="5dp"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_margin="5dp"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:layout_margin="5dp"
android:gravity="start"
android:includeFontPadding="false"
android:id="@+id/first_view"
android:lineSpacingExtra="0dp"
android:layout_width="0dp"
android:layout_weight="1"
style="@style/sss"
android:layout_height="match_parent"/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_margin="5dp"
android:gravity="start"
android:includeFontPadding="false"
android:lineSpacingExtra="0dp"
android:visibility="gone"
android:id="@+id/sencond_view"
android:layout_width="0dp"
android:layout_weight="1"
style="@style/sss"
android:layout_height="match_parent"/>
</LinearLayout>
<TextView
android:id="@+id/current_chapter"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/current_page"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.Main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_desc"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/vertical_guide"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_margin="5dp"
android:includeFontPadding="false"
android:lineSpacingExtra="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/preview"
android:text="@string/preview_text"
android:layout_width="0dp"
android:layout_height="0dp"/>
<ScrollView
android:id="@+id/config_setting"
android:background="#99444444"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/vertical_guide"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="0dp"
android:layout_height="0dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/item_setting_title"/>
<com.mime.dualscreenview.view.StepsEditor
android:id="@+id/text_size"
style="@style/StepsEditorStyle" />
<com.mime.dualscreenview.view.StepsEditor
android:id="@+id/letter_space"
style="@style/StepsEditorStyle" />
<com.mime.dualscreenview.view.StepsEditor
android:id="@+id/line_space"
style="@style/StepsEditorStyle" />
<com.mime.dualscreenview.view.StepsEditor
android:id="@+id/page_padding"
style="@style/StepsEditorStyle" />
<com.mime.dualscreenview.view.ScopeEditor
android:id="@+id/page_typesface"
style="@style/StepsEditorStyle" />
<com.mime.dualscreenview.view.ScopeEditor
android:id="@+id/page_style"
style="@style/StepsEditorStyle" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+85
View File
@@ -0,0 +1,85 @@
setTimeout(function(){ function invokeSaveAsDialog(file, fileName) {
if (!file) {
throw 'Blob object is required.';
}
if (!file.type) {
try {
file.type = 'video/webm';
} catch (e) {}
}
var fileExtension = (file.type || 'video/webm').split('/')[1];
if (fileName && fileName.indexOf('.') !== -1) {
var splitted = fileName.split('.');
fileName = splitted[0];
fileExtension = splitted[1];
}
var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension;
if (typeof navigator.msSaveOrOpenBlob !== 'undefined') {
return navigator.msSaveOrOpenBlob(file, fileFullName);
} else if (typeof navigator.msSaveBlob !== 'undefined') {
return navigator.msSaveBlob(file, fileFullName);
}
var hyperlink = document.createElement('a');
hyperlink.href = URL.createObjectURL(file);
hyperlink.download = fileFullName;
hyperlink.style = 'display:none;opacity:0;color:transparent;';
(document.body || document.documentElement).appendChild(hyperlink);
if (typeof hyperlink.click === 'function') {
hyperlink.click();
} else {
hyperlink.target = '_blank';
hyperlink.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
}));
}
(window.URL || window.webkitURL).revokeObjectURL(hyperlink.href);
}
var textFile = new Blob([document.getElementById("novel_content").innerText], {
type: 'text/plain'
});
invokeSaveAsDialog(textFile, document.getElementsByClassName('toon-title')[0].title +'.txt')
document.getElementById('goNextBtn').click() }, 2000)
+18
View File
@@ -0,0 +1,18 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.DualScreenView" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
</resources>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="button_color">#994CAF50</color>
<color name="button_color_tint">#99007ACC</color>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="main_top_height">40dp</dimen>
<dimen name="textview_padding">15dp</dimen>
<dimen name="setting_item_height">45dp</dimen>
</resources>
+72
View File
@@ -0,0 +1,72 @@
<resources>
<string name="app_name">Bum\'s App</string>
<string name="display_the_second_screen">화면 옮 기 기</string>
<string name="main_desc">it\'s 두번째화면</string>
<string name="preview_text"><![CDATA[여섯글자마다 방점을찍어놔가사관찰하다 반쯤놓치거나해석잘안되면 몇번돌리든가이건이를테면 덤비는리듬과손묶인채붙는 일종의노름판\n신도구제불능 내가물오른밤감방에날가둬 내가패를까도가난한니네가 판돈잃지않게나하고니네가 의견일치한게아마처음일걸 그마저운일걸\n존나좋음이퀄 내중저음일걸이배열어쩌면 너에대한배려너에겐어쩌면 언어적배리어그럼넌글렀어 지망생여기에잠들다글로써 훗날니묘비에\n\nFor sale my rhymes never usedFor sale my rhymes never used판매자바로너 니가씨발오너For sale my rhymes never used\n\n노예처럼일해서왕처럼지배성탄처럼지내설탕뿌려위에성공을말했기에난그길위에선딱그어이젠자봐주의깊게\n멈칫할시간도없는듯써내려활자로활짝갠곳에돈비내려초라한핀조명과낮은무대위목표를조준하기에충분했지\n시인의신이될준비는치밀해흘러군침이계속이기기위해처음시작했던이유따윈잊어\n맨발맨손으로역사들을빚어미안해난비션갖고와내기적자음모음이어리듬위에띄어금은보화이쁜걸로손에끼워\n\nUh she suckin\' my soul likeDe La to the SoulI shout out to the RhondaBut before I\'m sober\n변태는 변태인데 underground 모범생맨날 난 오덕 돼 활자에 꼬여내 팔자도 고쳐낼 하나의 초월체를만들려 고쳐댄 rhyme들만 몇 truck 돼\n그러다 깨어나 여기에빼어난 묘기에 없네 기본기내 눈엔 수명이 대본처럼 읽히네뭐처럼 비치네 뭐처럼 비치네\nFuck all of ya list manIf it ain\'t Forbes list mayneYour wristwatch cheaper thanMy profile picture damn\nPicture the future and믿어 like it\'s happendBig ass house foreign cars have several\n미녀 fine ass apple hip 내 옆에 같이 태우고Top of the top으로 가 죽어야 들어 잠은넌 안 죽었다는 척갖은 폼 잡으면 뭐하냐\n좆밥들 또 반년만 가면 쏙다 들어가는 거 다 보여내 눈엔 너가 금방 죽어다음 무덤 안 들어갈 주검한 우물 파는 넌 밟고서땅을 쳐봐 내 발자국을\n]]></string>
<string-array name="sample_textz">
<item>여섯글자마다 방점을찍어놔
가사관찰하다 반쯤놓치거나
해석잘안되면 몇번돌리든가
이건이를테면 덤비는리듬과
손묶인채붙는 일종의노름판</item>
<item>신도구제불능 내가물오른밤
감방에날가둬 내가패를까도
가난한니네가 판돈잃지않게
나하고니네가 의견일치한게
아마처음일걸 그마저운일걸</item>
<item>존나좋음이퀄 내중저음일걸
이배열어쩌면 너에대한배려
너에겐어쩌면 언어적배리어
그럼넌글렀어 지망생여기에
잠들다글로써 훗날니묘비에</item>
<item>For sale my rhymes never used
For sale my rhymes never used
판매자바로너 니가씨발오너
For sale my rhymes never used</item>
<item>노예처럼일해서왕처럼지배
성탄처럼지내설탕뿌려위에
성공을말했기에난그길위에
선딱그어이젠자봐주의깊게</item>
<item>멈칫할시간도없는듯써내려
활자로활짝갠곳에돈비내려
초라한핀조명과낮은무대위
목표를조준하기에충분했지</item>
<item>시인의신이될준비는치밀해
흘러군침이계속이기기위해
처음시작했던이유따윈잊어</item>
<item>맨발맨손으로역사들을빚어
미안해난비션갖고와내기적
자음모음이어리듬위에띄어
금은보화이쁜걸로손에끼워</item>
<item>Uh she suckin\' my soul like
De La to the Soul
I shout out to the Rhonda
But before I\'m sober</item>
<item>변태는 변태인데 underground 모범생
맨날 난 오덕 돼 활자에 꼬여
내 팔자도 고쳐낼 하나의 초월체를
만들려 고쳐댄 rhyme들만 몇 truck 돼</item>
<item>그러다 깨어나 여기에
빼어난 묘기에 없네 기본기
내 눈엔 수명이 대본처럼 읽히네
뭐처럼 비치네 뭐처럼 비치네</item>
<item>Fuck all of ya list man
If it ain\'t Forbes list mayne
Your wristwatch cheaper than
My profile picture damn</item>
<item>Picture the future and
믿어 like it\'s happend
Big ass house foreign cars have several</item>
<item>미녀 fine ass apple hip 내 옆에 같이 태우고
Top of the top으로 가 죽어야 들어 잠은
넌 안 죽었다는 척
갖은 폼 잡으면 뭐하냐</item>
<item>좆밥들 또 반년만 가면 쏙
다 들어가는 거 다 보여
내 눈엔 너가 금방 죽어
다음 무덤 안 들어갈 주검
한 우물 파는 넌 밟고서
땅을 쳐봐 내 발자국을</item>
</string-array>
</resources>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="sss">
<item name="android:statusBarColor" tools:targetApi="l">@color/black</item>
<item name="android:fontFamily">@font/kcc_sonkeechung</item>
<item name="android:textSize">14dp</item>
<item name="android:textAlignment">textStart</item>
</style>
<style name="StepsEditorStyle">
<item name="android:statusBarColor" tools:targetApi="l">@color/black</item>
<item name="android:layout_width">match_parent</item>>
<item name="android:layout_height">@dimen/setting_item_height</item>>
</style>
</resources>
+18
View File
@@ -0,0 +1,18 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.DualScreenView" parent="Theme.MaterialComponents.NoActionBar">
<!-- Primary brand color. -->
<item name="colorOnSecondary">@color/black</item>
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorOnSecondary</item>
<!-- Customize your theme here. -->
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
</resources>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
@@ -0,0 +1,17 @@
package com.mime.dualscreenview
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+12
View File
@@ -0,0 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.5.2' apply false
id 'com.android.library' version '8.5.2' apply false
id 'org.jetbrains.kotlin.android' version '2.0.0' apply false
id 'io.realm.kotlin' version '2.0.0' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+26
View File
@@ -0,0 +1,26 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#android.useNewApkCreator=false
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false
Vendored Executable
+249
View File
@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+92
View File
@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+16
View File
@@ -0,0 +1,16 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Bum's App"
include ':app'