commit 389182c38ac62d36d77ce36f40334ce175c0f84e Author: lunaticbum Date: Sun Mar 26 20:40:07 2023 +0900 일단 커밋 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ecbc27 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# tokkiz diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..b64a666 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'io.realm.kotlin' +} + +android { + namespace 'com.mime.dualscreenview' + compileSdk 33 + + defaultConfig { + applicationId "com.mime.dualscreenview" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + 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' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.collection:collection-ktx:1.2.0' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.code.gson:gson:2.10.1' + + implementation files('libs/DualScreen.jar') + + implementation 'io.realm.kotlin:library-base:1.6.0' + + + + testImplementation 'junit:junit:4.13.2' + +} \ No newline at end of file diff --git a/app/libs/DualScreen.jar b/app/libs/DualScreen.jar new file mode 100644 index 0000000..d1c1d71 Binary files /dev/null and b/app/libs/DualScreen.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/mime/dualscreenview/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/mime/dualscreenview/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d37707e --- /dev/null +++ b/app/src/androidTest/java/com/mime/dualscreenview/ExampleInstrumentedTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..06fede1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/BaseAppication.kt b/app/src/main/java/com/mime/dualscreenview/BaseAppication.kt new file mode 100644 index 0000000..98e97bb --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/BaseAppication.kt @@ -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() + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/activity/Base.kt b/app/src/main/java/com/mime/dualscreenview/activity/Base.kt new file mode 100644 index 0000000..10e1328 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/activity/Base.kt @@ -0,0 +1,135 @@ +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 + + +open class Base : AppCompatActivity() { + + inner class BaseHandler(looper : Looper = Looper.getMainLooper()) : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + } +// +// override fun dispatchMessage(msg: Message) { +// super.dispatchMessage(msg) +// } +// +// override fun getMessageName(message: Message): String { +// return super.getMessageName(message) +// } +// +// override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean { +// return super.sendMessageAtTime(msg, uptimeMillis) +// } + } + 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 + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/activity/Intro.kt b/app/src/main/java/com/mime/dualscreenview/activity/Intro.kt new file mode 100644 index 0000000..aaa6178 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/activity/Intro.kt @@ -0,0 +1,572 @@ +package com.mime.dualscreenview.activity + +import android.app.ActivityOptions +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Color +import android.hardware.display.DisplayManager +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.KeyEvent +import android.view.View +import android.view.View.* +import android.webkit.WebView +import android.widget.ArrayAdapter +import android.widget.Button +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.ProgressBar +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatButton +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.updateLayoutParams +import com.google.gson.Gson +import com.lge.display.DisplayManagerHelper +import com.mime.dualscreenview.R +import com.mime.dualscreenview.common.Blog +import com.mime.dualscreenview.dto.BookPageInfos +import com.mime.dualscreenview.dto.LastInfo +import com.mime.dualscreenview.view.PagedTextLayout +import com.mime.dualscreenview.view.PagedTextViewInterface +import com.mime.dualscreenview.view.TouchArea +import com.mime.dualscreenview.webcontents.BaseWebContentsViewer +import com.mime.dualscreenview.webcontents.MainControllInterface +import com.mime.dualscreenview.webcontents.contentsinfo.Booktoki +import com.mime.dualscreenview.webcontents.contentsinfo.GotoSomeWhere +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.ext.copyFromRealm +import io.realm.kotlin.ext.query + + +class Intro : Base() , MainControllInterface, PagedTextViewInterface { + + private var displayManagerHelper: DisplayManagerHelper? = null + + // This callbacks where receive events from the cover + private var coverDisplayCallback: MainCoverDisplayCallback? = null + private var smartCoverCallback: MainSmartCoverCallback? = null + + // Save previous state of dual screens + private var prevDualScreenState = DisplayManagerHelper.STATE_UNMOUNT + + private var isLGDualScreen: Boolean = false + private lateinit var mBaseWebContentsViewer : BaseWebContentsViewer + var lastInfo : LastInfo? = null + + val colors = arrayOf>( + arrayOf("#E1F5FE", "#263238"), + arrayOf("#F0F4C3", "#37474F"), + arrayOf("#ECEFF1", "#455A64"), + arrayOf("#E0F7FA", "#263238"), + arrayOf("#F5F5F5", "#263238"), + arrayOf("#ECEFF1", "#263238"), + arrayOf("#F8BBD0", "#263238"), + arrayOf("#E6EE9C", "#455A64"), + arrayOf("#CFD8DC", "#455A64"), + arrayOf("#FFF59D", "#37474F") + ) + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + Blog.LOGD(log= "onConfigurationChanged ${this::class.java.name} >> newConfig ${newConfig}") + mBaseWebContentsViewer.webview.reload() + } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Blog.LOGD(log= "onCreate ${this::class.java.name} >> savedInstanceState ${savedInstanceState}") + + setContentView(R.layout.intro) + mBaseWebContentsViewer = BaseWebContentsViewer(findViewById(R.id.menu_web),this) + try { + // Try to construct the DisplayMangerHelper. + // If it isn't successful, this device isn't LG dual screens + displayManagerHelper = DisplayManagerHelper(applicationContext) + coverDisplayCallback = MainCoverDisplayCallback() + smartCoverCallback = MainSmartCoverCallback() + + // Register the callbacks for covers + displayManagerHelper?.registerCoverDisplayEnabledCallback( + applicationContext.packageName, + coverDisplayCallback + ) + displayManagerHelper?.registerSmartCoverCallback(smartCoverCallback) + isLGDualScreen = true + } catch (e: Exception) { + isLGDualScreen = false + Log.e(TAG, "This device isn't LG dual screens", e) + } + + findViewById(R.id.btn_list).setOnClickListener { v -> + mBaseWebContentsViewer?.findListItem {result -> + var infos = Gson().fromJson(result, BookPageInfos::class.java) + showList(infos) + } + } + + findViewById(R.id.btn_rotate).setOnClickListener { v-> + switcvhOrient() + } + findViewById(R.id.btn_setting).setOnClickListener { v-> + showStyleList() + } + findViewById(R.id.btn_home).setOnClickListener { v-> + mBaseWebContentsViewer.loadContents(Booktoki()) + } + + val config = RealmConfiguration.Builder(setOf(LastInfo::class)).schemaVersion(1) + .build() + val realm = Realm.open(config) + + + try { + lastInfo = realm?.query()?.find()?.last()?.copyFromRealm() + } catch (e : Exception) { + + } + if (lastInfo != null) { + setRequestedOrientation(lastInfo!!.displayOrientation); + mBaseWebContentsViewer.loadLastInfo(lastInfo!!) + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + mBaseWebContentsViewer.loadContents(Booktoki()) + } + Blog.LOGD(log ="Successfully opened realm: ${realm.configuration.name}") + realm.close() + } + fun switcvhOrient(){ + val windowManager = getSystemService(WINDOW_SERVICE) + val configuration: Configuration = getResources().getConfiguration() + setRequestedOrientation( + if(configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } + else {ActivityInfo.SCREEN_ORIENTATION_PORTRAIT} + ) + } + + fun showList(infos: BookPageInfos) { + val builderSingle: AlertDialog.Builder = AlertDialog.Builder(this@Intro) + builderSingle.setTitle("Select One Name:-") + val arrayAdapter = + ArrayAdapter(this@Intro, android.R.layout.select_dialog_singlechoice) + arrayAdapter.addAll(infos.getTitleArray()) + + + builderSingle.setNegativeButton("cancel", + DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() }) + + builderSingle.setAdapter(arrayAdapter, + DialogInterface.OnClickListener { dialog, which -> + val strName = arrayAdapter.getItem(which) + val builderInner: AlertDialog.Builder = AlertDialog.Builder(this@Intro) + builderInner.setMessage(strName) + builderInner.setTitle("Your Selected Item is") + builderInner.setPositiveButton("Ok", + DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() }) + builderInner.show() + }) + var ddddd= builderSingle.create() + ddddd.setOnShowListener { d-> + (d as? AlertDialog)?.let{ + it.listView?.smoothScrollToPosition(currentChapter) + } + } + + ddddd.show() + } + + fun showStyleList() { + val builderSingle: AlertDialog.Builder = AlertDialog.Builder(this@Intro) + builderSingle.setTitle("Select One Name:-") + val arrayAdapter = + ArrayAdapter(this@Intro, android.R.layout.select_dialog_singlechoice) + var styleNum = 1; + for (a in colors) { + arrayAdapter.add("스타일 ${styleNum}") + styleNum = styleNum.inc() + } + + + builderSingle.setNegativeButton("cancel", + DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() }) + + builderSingle.setAdapter(arrayAdapter, + DialogInterface.OnClickListener { dialog, which -> + val color = colors.get(which) + val builderInner: AlertDialog.Builder = AlertDialog.Builder(this@Intro) + builderInner.setMessage("스타일 ${which + 1}") + + builderInner.setTitle("Your Selected Item is") + builderInner.setPositiveButton("Ok", + DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() + pg?.setColorStyle(color) + findViewById(R.id.intro_bg).setBackgroundColor(Color.parseColor(color.get(1))) + }) + builderInner.show() + }) + var ddddd= builderSingle.create() + ddddd.setOnShowListener { d-> + (d as? AlertDialog)?.let{ + it.listView?.smoothScrollToPosition(currentChapter) + } + } + + ddddd.show() + } + + + override fun onDestroy() { + // Remove all callbacks when this activity is destroyed + displayManagerHelper?.unregisterCoverDisplayEnabledCallback(applicationContext.packageName) + displayManagerHelper?.unregisterSmartCoverCallback(smartCoverCallback) + super.onDestroy() + } + + /** + * Convert cover display states to string to serve for logging + * + * @param state is the value integer of state + * @return a string for this state + */ + private fun coverDisplayStateToString(state: Int): String { + return when (state) { + DisplayManagerHelper.STATE_UNMOUNT -> "STATE_UNMOUNT" + DisplayManagerHelper.STATE_DISABLED -> "STATE_DISABLED" + DisplayManagerHelper.STATE_ENABLED -> "STATE_ENABLED" + else -> "UNKNOWN_STATE" + } + } + + /** + * Convert smart cover display states to string to serve for logging + * + * @param state is the value integer of state + * @return a string for this state + */ + private fun smartCoverStateToString(state: Int): String { + return when (state) { + DisplayManagerHelper.STATE_COVER_OPENED -> "STATE_COVER_OPENED" + DisplayManagerHelper.STATE_COVER_CLOSED -> "STATE_COVER_CLOSED" + DisplayManagerHelper.STATE_COVER_FLIPPED_OVER -> "STATE_COVER_FLIPPED_OVER" + else -> "UNKNOWN_STATE" + } + } + + /** + * Navigate to the second screen. + * + * See more at https://developer.android.com/guide/topics/ui/foldables?#using_secondary_screens + */ + private fun toSecondScreen(screenNumStr : String) { + + var screenNum = screenNumStr.toInt() + // DisplayManager manages the properties of attached displays. + val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + + // List displays was attached + val displays = displayManager.displays + + if (displays.size > screenNum) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Activity options are used to select the display screen. + val options = ActivityOptions.makeBasic() + // Select the display screen that you want to show the second activity + options.launchDisplayId = displays[screenNum].displayId + + // To display on the second screen that your intent must be set flag to make + // single task (combine FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_NEW_TASK) + // or you also set it in the manifest (see more at the manifest file) + startActivity( + Intent(this@Intro, Main::class.java).apply { + + }, + options.toBundle() + ) + } + } else { + Toast.makeText(this, "Not found the second screen", Toast.LENGTH_SHORT).show() + } + } + + private inner class MainCoverDisplayCallback : DisplayManagerHelper.CoverDisplayCallback() { + override fun onCoverDisplayEnabledChangedCallback(state: Int) { + displayManagerHelper?.coverDisplayState?.let { + Log.i(TAG, "Current DualScreen Callback state: ${coverDisplayStateToString(it)}") + } + if (prevDualScreenState != state) { + when (state) { + DisplayManagerHelper.STATE_UNMOUNT -> { + Log.i(TAG, "Changed DualScreen State to STATE_UNMOUNT") + } + DisplayManagerHelper.STATE_DISABLED -> { + Log.i(TAG, "Changed DualScreen State to STATE_DISABLED") + } + DisplayManagerHelper.STATE_ENABLED -> { +// toSecondScreen() + Log.i(TAG, "Changed DualScreen State to STATE_ENABLED") + } + } + prevDualScreenState = state + } + } + } + + private inner class MainSmartCoverCallback : DisplayManagerHelper.SmartCoverCallback() { + override fun onTypeChanged(type: Int) { + Log.i(TAG, "SmartCoverCallback type: ${displayManagerHelper?.coverType}") + } + + override fun onStateChanged(state: Int) { + displayManagerHelper?.coverState?.let { + Log.i(TAG, "Current SmartCoverCallback state: ${smartCoverStateToString(it)}") + } + when (state) { + DisplayManagerHelper.STATE_COVER_OPENED -> { + Log.i(TAG, "Received SmartCoverCallback is STATE_COVER_OPENED") + } + DisplayManagerHelper.STATE_COVER_CLOSED -> { + Log.i(TAG, "Received SmartCoverCallback is STATE_COVER_CLOSED") + } + DisplayManagerHelper.STATE_COVER_FLIPPED_OVER -> { + Log.i(TAG, "Received SmartCoverCallback is STATE_COVER_FLIPPED_OVER") + } + } + } + } + var pg : PagedTextLayout? = null; + + var onNextClickAction: GotoSomeWhere? = null + override fun showNextBtn(find : Boolean , onClickAction: GotoSomeWhere) { + onNextClickAction = onClickAction + findViewById(R.id.btn_right)?.let{ + it.text = "다음 페이지" + it.setOnClickListener { + actionNextEvent() + } + it.visibility= if(find) VISIBLE else GONE + } + } + + fun actionNextEvent() { + if (pg != null && pg!!.visibility == View.VISIBLE && pg!!.size() > 0 && (pg!!.current() < pg!!.size() - 1) ) { + pg!!.doNext() + updateLastInfo(pg!!) + }else { + onNextClickAction?.let { it() } + } + } + var onPrevClickAction: GotoSomeWhere? = null + override fun showPrevBtn(find : Boolean, onClickAction: GotoSomeWhere) { + onPrevClickAction = onClickAction + findViewById(R.id.btn_left)?.let{ + it.text = "이전 페이지" + it.setOnClickListener { + actionPrevEvent() + } + it.visibility= if(find) VISIBLE else GONE + } + } + + + fun actionPrevEvent() { + if (pg != null && pg!!.visibility == View.VISIBLE && pg!!.size() > 0 && pg!!.current() > 0 ) { + pg!!.doPrev() + updateLastInfo(pg!!) + } else { + onPrevClickAction?.let{ it() } + } + } + + override fun onBackPressed() { + var layer = findViewById(R.id.paged_layer) + + if (!didBackPress) { + firstBackPress() + return + } + + if (layer != null && layer.visibility == View.VISIBLE) { + didBackPress = false + layer.visibility = GONE + onTouch(TouchArea.Center) + return + } + + if (mBaseWebContentsViewer.webview.canGoBack()) { + mBaseWebContentsViewer.webview.goBack() + return + } + + if (!didBackPress) { + firstBackPress() + return + } + + didBackPress = false + + super.onBackPressed() + } + + + override fun showAlert(alert: String) { + Log.i(TAG,"showAlert >> " + alert) + } + + override fun onLoadedContents(contents: String) { + pg = null; + findViewById(R.id.paged_layer).apply { + if (contents != null) { + pg = this + visibility = VISIBLE + mPagedTextViewInterface = this@Intro + setText(contents.replace("\\n", System.getProperty("line.separator"))) + setTextSize(22f) + if(lastInfo != null && lastInfo!!.pageUrl.equals(mBaseWebContentsViewer.webview.url)) { + this@Intro.findViewById(R.id.progress)?.visibility = VISIBLE + pg?.postDelayed({ + next(lastInfo!!.pageIndex) + pg?.post { + this@Intro.findViewById(R.id.progress)?.visibility = GONE + } + },1000) + } + forceUpdateUI() + } + } + + Log.i(TAG,"onLoadedContents >> " + contents) + } + + var currentTitle : String = "" + var currentChapter : Int = 0 + + override fun onFindTitle(contents: String) { + findViewById(R.id.textView).text = contents + var testRegex = """[^0-9]""".toRegex(); + Blog.LOGI(TAG,"onFindTitle >> " + contents + " ::: ${testRegex.replace(contents,"")}") + if(contents.contains("-")) { + currentTitle = contents.split("-")[0] + currentChapter = testRegex.replace(contents.split("-")[1],"").toInt() + } + + + } + + override fun onStartLoad() { + findViewById(R.id.progress).visibility = VISIBLE + } + + override fun completePageLoad(lastInfo: LastInfo) { + if(this.lastInfo == null || !(this.lastInfo?.pageUrl.equals(lastInfo?.pageUrl))) { + saveLastInfo(lastInfo) + } + findViewById(R.id.progress).visibility = GONE + } + + fun saveLastInfo(lastInfo: LastInfo) { + val windowManager = getSystemService(WINDOW_SERVICE) + val configuration: Configuration = getResources().getConfiguration() + val config = RealmConfiguration.Builder(setOf(LastInfo::class)) + .schemaVersion(1) + .build() + val realm = Realm.open(config) + realm.writeBlocking { + lastInfo.displayOrientation = configuration.orientation + copyToRealm(lastInfo) + } + Blog.LOGD(log ="Successfully opened realm: ${realm.configuration.name}") + realm.close() + } + + fun updateLastInfo(pagedTextLayout: PagedTextLayout) { + val windowManager = getSystemService(WINDOW_SERVICE) + val configuration: Configuration = getResources().getConfiguration() + val config = RealmConfiguration.Builder(setOf(LastInfo::class)) + .schemaVersion(1) + .build() + val realm = Realm.open(config) + + realm.writeBlocking { + var info = this.query()?.find()?.last() + info?.pageIndex = pagedTextLayout.current() + info?.displayOrientation = configuration.orientation + } + + Blog.LOGD(log ="Successfully opened realm: ${realm.configuration.name}") + realm.close() + } + override fun onKeyClick(keyCode: Int): Boolean { + when(keyCode) { + KeyEvent.KEYCODE_VOLUME_DOWN ->{actionNextEvent()} + KeyEvent.KEYCODE_VOLUME_UP ->{actionPrevEvent()} + KeyEvent.KEYCODE_VOLUME_MUTE -> {actionNextEvent()} + + } + return super.onKeyClick(keyCode) + } + + override fun onTouch(touchArea: TouchArea) { + Blog.LOGD(log="onTouch") + when (touchArea) { + TouchArea.Center-> { + findViewById(R.id.btn_right).visibility = VISIBLE + findViewById(R.id.btn_left).visibility = VISIBLE + findViewById(R.id.btn_setting).visibility = VISIBLE + + findViewById(R.id.textView).visibility = VISIBLE + findViewById(R.id.btn_home).visibility = VISIBLE + findViewById(R.id.btn_list).visibility = VISIBLE + findViewById(R.id.btn_menu).visibility = VISIBLE + findViewById(R.id.btn_rotate).visibility = VISIBLE + + } + TouchArea.Right -> { + actionNextEvent() + } + TouchArea.Left-> { + actionPrevEvent() + } + else -> { + + } + } + + + } + + override fun onSwipeLeft() { + Blog.LOGD(log="onSwipeLeft") + actionNextEvent() + + } + + override fun onSwipeRight() { + Blog.LOGD(log="onSwipeRight") + actionPrevEvent() + } + + + override fun onTimeoverTouch() { + Blog.LOGD(log="onTimeoverTouch") + findViewById(R.id.btn_right).visibility = GONE + findViewById(R.id.btn_left).visibility = GONE + findViewById(R.id.btn_setting).visibility = GONE + + findViewById(R.id.textView).visibility = GONE + findViewById(R.id.btn_home).visibility = GONE + findViewById(R.id.btn_list).visibility = GONE + findViewById(R.id.btn_menu).visibility = GONE + findViewById(R.id.btn_rotate).visibility = GONE + + } + + companion object { + private const val TAG = "DualScreenStatus" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/activity/Main.kt b/app/src/main/java/com/mime/dualscreenview/activity/Main.kt new file mode 100644 index 0000000..76f03a5 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/activity/Main.kt @@ -0,0 +1,8 @@ +package com.mime.dualscreenview.activity + +import android.app.Activity +import androidx.appcompat.app.AppCompatActivity + +class Main : Base() { + +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/common/BLog.kt b/app/src/main/java/com/mime/dualscreenview/common/BLog.kt new file mode 100644 index 0000000..c6768d9 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/common/BLog.kt @@ -0,0 +1,50 @@ +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) + } + + 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 -> {} + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/dto/BookPageInfo.kt b/app/src/main/java/com/mime/dualscreenview/dto/BookPageInfo.kt new file mode 100644 index 0000000..9d538c9 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/dto/BookPageInfo.kt @@ -0,0 +1,15 @@ +package com.mime.dualscreenview.dto + + +class BookPageInfos { +var list : ArrayList? = null + fun getTitleArray() : ArrayList { + var arrayList = ArrayList() + list?.forEach { arrayList.add(it.title ?: "") } + return arrayList + } +} +class BookPageInfo { + var title : String? = "" + var lastPath : String? = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/dto/PageInfo.kt b/app/src/main/java/com/mime/dualscreenview/dto/PageInfo.kt new file mode 100644 index 0000000..d2c8024 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/dto/PageInfo.kt @@ -0,0 +1,24 @@ +package com.mime.dualscreenview.dto + +import android.content.pm.ActivityInfo +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey +import org.mongodb.kbson.ObjectId + + +class LastInfo() : RealmObject { + var _id : String = "UniqLastId" + var pageUrl : String = "" + var title : String = "" + var contentsData : String = "" + var pageIndex : Int = 0 + var contentsName : String = "" + var displayOrientation : Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT +} + +class Bookmark() : RealmObject { + @PrimaryKey + var pageUrl : String = "" +} + + diff --git a/app/src/main/java/com/mime/dualscreenview/view/OnSwipeTouchListener.kt b/app/src/main/java/com/mime/dualscreenview/view/OnSwipeTouchListener.kt new file mode 100644 index 0000000..400ac2f --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/view/OnSwipeTouchListener.kt @@ -0,0 +1,71 @@ +package com.mime.dualscreenview.view + +import android.content.Context +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View + + +enum class TouchArea { + Left,Center,Right +} + +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 onSingleTap(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 onSingleTapUp(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.onSingleTapUp(e) + } + + override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + val distanceX = e2.x - e1.x + val distanceY = e2.y - e1.y + 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 + } + return false + } + } + init { + gestureDetector = GestureDetector(context, GestureListener()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/view/PagedTextLayout.kt b/app/src/main/java/com/mime/dualscreenview/view/PagedTextLayout.kt new file mode 100644 index 0000000..ed1b8c6 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/view/PagedTextLayout.kt @@ -0,0 +1,174 @@ +package com.mime.dualscreenview.view + +import android.content.Context +import android.graphics.Color +import android.os.Handler +import android.util.AttributeSet +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 hiddenTextView : PagedTextView? = null + var guideLine : Guideline? = null + var pageList: ArrayList? = null + var text : String = "" + set(new) { + field = new + hiddenTextView?.text = text + hiddenTextView?.forceLayout() + } + + private val hanler = Handler() + var mPagedTextViewInterface : PagedTextViewInterface? = null + val touchTimeover = Runnable { + mPagedTextViewInterface?.onTimeoverTouch() + } + + fun initView(context: Context) { + inflate(context, R.layout.layout_textviewer, this) + mainTextView = findViewById(R.id.first_view) + sencondTextView = findViewById(R.id.sencond_view) + hiddenTextView = findViewById(R.id.hidden_view) + guideLine = findViewById(R.id.center_guide) + hiddenTextView?.mPagedTextGenerateInterface = this + hanler.removeCallbacks(touchTimeover) + setOnTouchListener(object : OnSwipeTouchListener(context) { + override fun onSwipeLeft() { + mPagedTextViewInterface?.onSwipeLeft() + } + + override fun onSwipeRight() { + mPagedTextViewInterface?.onSwipeRight() + } + + override fun onSingleTap(touchArea: TouchArea) { + var isCenterTouch = TouchArea.Center.equals(touchArea) + hanler.removeCallbacks(touchTimeover) + mPagedTextViewInterface?.onTouch(touchArea) + hanler?.postDelayed(touchTimeover, 3000L) + } + }) + } + + fun layoutChange(needDualPage: Boolean) { + Blog.LOGD(log = "layoutChange>> ${this::class.java.name}") + if (needDualPage) { + findViewById(R.id.center_guide).updateLayoutParams { + guidePercent = 0.5f + } + } else { + findViewById(R.id.center_guide).updateLayoutParams { + guidePercent = 1f + } + } + } + + 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) { + 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) + } + } + + fun setColorStyle(colors : Array) { + 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 (guideLine?.layoutParams as ConstraintLayout.LayoutParams).guidePercent != 1f + } + + fun setPageBy(num : Int) { + currentPage = num + var realPage = if(isDualPage()) currentPage * 2 else currentPage + 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 current(): Int = currentPage + fun doNext() { + setPageBy(currentPage.inc()) + } + + fun doPrev() { + setPageBy(currentPage.dec()) + } + + fun forceUpdateUI() { + mPagedTextViewInterface?.onTouch(TouchArea.Center) + hiddenTextView?.doUpdate() + hanler?.postDelayed(touchTimeover, 3000L) + } + + +} + diff --git a/app/src/main/java/com/mime/dualscreenview/view/PagedTextView.kt b/app/src/main/java/com/mime/dualscreenview/view/PagedTextView.kt new file mode 100644 index 0000000..0598030 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/view/PagedTextView.kt @@ -0,0 +1,242 @@ +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 com.mime.dualscreenview.common.Blog +import kotlin.math.min + + +interface PagedTextViewInterface { + fun onTouch(touchArea: TouchArea) + fun onTimeoverTouch() + fun onSwipeLeft() + fun onSwipeRight() +} + +interface PagedTextGenerateInterface { + fun completePagination(pageList: ArrayList) +} + + +class PagedTextView : AppCompatTextView { + + private var needPaginate = false + private var isPaginating = false + private val pageList = arrayListOf() + 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() { + isPaginating = true + text = pageList[pageIndex] + isPaginating = false + } + + override fun setText(text: CharSequence?, type: BufferType?) { + if (!isPaginating) { + 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 + } + + 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() { + pageList.clear() + Blog.LOGD(log = "paginate>> ${this::class.java.name}") + val layout = from(layout) + val lines = min(maxLines, layout.lineCount) + var startOffset = 0 + val heightWithoutPaddings = pageHeight - 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( + 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) + .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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContents.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContents.kt new file mode 100644 index 0000000..5f93fb0 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContents.kt @@ -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 { + 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 + } + } + +} diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContentsViewer.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContentsViewer.kt new file mode 100644 index 0000000..e11a676 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/BaseWebContentsViewer.kt @@ -0,0 +1,169 @@ +package com.mime.dualscreenview.webcontents + +import android.app.AlertDialog +import android.graphics.Bitmap +import android.net.http.SslError +import android.util.Log +import android.webkit.* +import android.widget.Toast +import com.mime.dualscreenview.dto.LastInfo +import com.mime.dualscreenview.webcontents.contentsinfo.DidFindContents + + +open class BaseWebContentsViewer { + + var currentContentsProvider : BaseWebContents? = null + lateinit var webview : WebView + lateinit var mainControllInterface : MainControllInterface + + constructor(webview : WebView, mainControllInterface : MainControllInterface ) { + this.webview = webview + this.mainControllInterface = mainControllInterface + webview.webChromeClient = rootWebChromeClient + webview.webViewClient = rootWebViewClient + webview.settings.textZoom = 100 + 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) + } + + 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 { + currentContentsProvider = WebContentsManger.getBaseWebContentsBy(it.contentsName) + webview.loadUrl(it.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 = 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) +// handler?.proceed() +// } +// override fun onReceivedHttpError( +// view: WebView?, +// request: WebResourceRequest?, +// errorResponse: WebResourceResponse? +// ) { +// super.onReceivedHttpError(view, request, errorResponse) +// } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + view?.let { + currentContentsProvider?.doOnloaded(it , { result -> + result?.let { mainControllInterface.onLoadedContents(it) } + } , { + it?.let { mainControllInterface.onFindTitle(it) } + }, {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 + }) + } + }) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/DefineTypes.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/DefineTypes.kt new file mode 100644 index 0000000..36b4ab0 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/DefineTypes.kt @@ -0,0 +1,5 @@ +//package com.mime.dualscreenview.webcontents +// +//typealias ActionByBool = (Boolean) -> Unit +//typealias DidFindContents = (String?) -> Unit +//typealias GotoSomeWhere = () -> Unit diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/MainControllInterface.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/MainControllInterface.kt new file mode 100644 index 0000000..c694c22 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/MainControllInterface.kt @@ -0,0 +1,19 @@ +package com.mime.dualscreenview.webcontents + +import com.mime.dualscreenview.dto.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) + + +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/WebContentsManger.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/WebContentsManger.kt new file mode 100644 index 0000000..54693b6 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/WebContentsManger.kt @@ -0,0 +1,18 @@ +package com.mime.dualscreenview.webcontents + +import com.mime.dualscreenview.webcontents.contentsinfo.Booktoki + +object WebContentsManger { + val allContentsList : ArrayList = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/Booktoki.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/Booktoki.kt new file mode 100644 index 0000000..b1fa5c3 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/Booktoki.kt @@ -0,0 +1,68 @@ +package com.mime.dualscreenview.webcontents.contentsinfo + +import com.mime.dualscreenview.webcontents.BaseWebContents + +class Booktoki : BaseWebContents() { + + + + 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" + + "\t\t\t var children = document.getElementsByName('wr_id')[0].children;\n" + + " var maxCount = children.length\n" + + " const contentsArray = []\n" + + " for (i= 0; i < maxCount; i++) {\n" + + " contentsArray.push({'title' : children[i].text ,\n" + + " 'link' : children[i].value})\n" + + " }\n" + + " contentsArray.reverse() \n" + + "return {'list' :contentsArray}\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" + } + } + + 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();" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/ContentsInfoInterface.kt b/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/ContentsInfoInterface.kt new file mode 100644 index 0000000..b99d173 --- /dev/null +++ b/app/src/main/java/com/mime/dualscreenview/webcontents/contentsinfo/ContentsInfoInterface.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bookmark.png b/app/src/main/res/drawable/bookmark.png new file mode 100644 index 0000000..94b5801 Binary files /dev/null and b/app/src/main/res/drawable/bookmark.png differ diff --git a/app/src/main/res/drawable/circle_progressbar.xml b/app/src/main/res/drawable/circle_progressbar.xml new file mode 100644 index 0000000..a082738 --- /dev/null +++ b/app/src/main/res/drawable/circle_progressbar.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home.png b/app/src/main/res/drawable/home.png new file mode 100644 index 0000000..d6fb946 Binary files /dev/null and b/app/src/main/res/drawable/home.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/invoice.png b/app/src/main/res/drawable/invoice.png new file mode 100644 index 0000000..49426b1 Binary files /dev/null and b/app/src/main/res/drawable/invoice.png differ diff --git a/app/src/main/res/drawable/rotation.png b/app/src/main/res/drawable/rotation.png new file mode 100644 index 0000000..0a16bdd Binary files /dev/null and b/app/src/main/res/drawable/rotation.png differ diff --git a/app/src/main/res/layout/intro.xml b/app/src/main/res/layout/intro.xml new file mode 100644 index 0000000..6f0b89d --- /dev/null +++ b/app/src/main/res/layout/intro.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_textviewer.xml b/app/src/main/res/layout/layout_textviewer.xml new file mode 100644 index 0000000..83880e7 --- /dev/null +++ b/app/src/main/res/layout/layout_textviewer.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml new file mode 100644 index 0000000..0833a5b --- /dev/null +++ b/app/src/main/res/layout/main.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/raw/booktoki_js b/app/src/main/res/raw/booktoki_js new file mode 100644 index 0000000..179cb34 --- /dev/null +++ b/app/src/main/res/raw/booktoki_js @@ -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) \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..7d4da0d --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..0146907 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 35dp + 15dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..443bce2 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + DualScreenView + 화면 옮 기 기 + it\'s 두번째화면 + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..5edd024 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/mime/dualscreenview/ExampleUnitTest.kt b/app/src/test/java/com/mime/dualscreenview/ExampleUnitTest.kt new file mode 100644 index 0000000..9e69041 --- /dev/null +++ b/app/src/test/java/com/mime/dualscreenview/ExampleUnitTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..80bf6c4 --- /dev/null +++ b/build.gradle @@ -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 '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false + id 'org.jetbrains.kotlin.android' version '1.7.20' apply false + id 'io.realm.kotlin' version '1.6.0' apply false +} + + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..d311a30 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,24 @@ +# 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 \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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=. +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%" == "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%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..27f5736 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "DualScreenView" +include ':app'