├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── padlandlogo.png │ │ │ │ ├── intro_image1.png │ │ │ │ ├── intro_image2.png │ │ │ │ ├── intro_image3.png │ │ │ │ ├── intro_image4.png │ │ │ │ ├── background_selector.xml │ │ │ │ ├── group_button_selector.xml │ │ │ │ ├── ic_group_button_up.xml │ │ │ │ ├── ic_group_button_down.xml │ │ │ │ ├── ic_arrow_back.xml │ │ │ │ ├── ic_info.xml │ │ │ │ ├── ic_delete.xml │ │ │ │ ├── ic_close.xml │ │ │ │ ├── ic_group_add.xml │ │ │ │ ├── ic_document.xml │ │ │ │ ├── ic_document_add.xml │ │ │ │ ├── ic_edit.xml │ │ │ │ ├── ic_add.xml │ │ │ │ ├── ic_content_copy.xml │ │ │ │ ├── dashed_border.xml │ │ │ │ ├── ic_share.xml │ │ │ │ └── ic_settings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── values │ │ │ │ ├── bools.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── content_requests.xml │ │ │ │ └── styles.xml │ │ │ ├── values-large │ │ │ │ └── bools.xml │ │ │ ├── values-v21 │ │ │ │ └── dimens.xml │ │ │ ├── color │ │ │ │ ├── appintro_desc_color.xml │ │ │ │ └── appintro_title_color.xml │ │ │ ├── values-sw600dp │ │ │ │ └── dimens.xml │ │ │ ├── values-sw720dp-land │ │ │ │ └── dimens.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── recyclerview_item.xml │ │ │ │ ├── header_activity.xml │ │ │ │ ├── activity_settings.xml │ │ │ │ ├── header_dialog.xml │ │ │ │ ├── activity_pad_view.xml │ │ │ │ ├── activity_about.xml │ │ │ │ ├── dialog_new_padgroup.xml │ │ │ │ ├── dialog_group_pad.xml │ │ │ │ ├── server_list_recyclerview_item_server.xml │ │ │ │ ├── activity_server_list.xml │ │ │ │ ├── pad_list_recyclerview_item_pad.xml │ │ │ │ ├── dialog_auth.xml │ │ │ │ └── pad_list_recyclerview_item_padgroup.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── values-v35 │ │ │ │ └── styles.xml │ │ │ ├── drawable-v26 │ │ │ │ └── background_selector.xml │ │ │ ├── menu │ │ │ │ ├── padgroup_action_mode_menu.xml │ │ │ │ ├── pad_list.xml │ │ │ │ ├── server_action_mode_menu.xml │ │ │ │ ├── pad_action_mode_menu.xml │ │ │ │ ├── pad_info.xml │ │ │ │ └── pad_view.xml │ │ │ ├── xml │ │ │ │ └── preferences.xml │ │ │ └── values-ja │ │ │ │ └── strings.xml │ │ ├── ic_launcher-web.png │ │ ├── ic_launcher-playstore.png │ │ └── java │ │ │ └── com │ │ │ └── mikifus │ │ │ └── padland │ │ │ ├── PadlandApp.kt │ │ │ ├── Utils │ │ │ ├── Export │ │ │ │ ├── Maps │ │ │ │ │ ├── IGenericMap.kt │ │ │ │ │ └── DatabaseMap.kt │ │ │ │ ├── ExclusionStrategies │ │ │ │ │ └── IgnoreEntityIdStrategy.kt │ │ │ │ ├── TypeAdapters │ │ │ │ │ └── SqlDateTypeAdapter.kt │ │ │ │ └── ImportHelper.kt │ │ │ ├── PadShareHelper.kt │ │ │ ├── PadLandWebViewClient │ │ │ │ └── PadLandWebClientCallbacks.kt │ │ │ ├── PadClipboardHelper.kt │ │ │ ├── Views │ │ │ │ └── Helpers │ │ │ │ │ ├── ResizeableNestedScrollView.kt │ │ │ │ │ └── SpinnerHelper.kt │ │ │ ├── WhiteListMatcher.kt │ │ │ ├── CryptPad │ │ │ │ └── CryptPadUtils.kt │ │ │ ├── Intro │ │ │ │ └── LinkableAppIntroFragment.kt │ │ │ ├── PadUrl.kt │ │ │ └── PadServer.kt │ │ │ ├── ActionModes │ │ │ └── AnyActionModeActive.kt │ │ │ ├── Adapters │ │ │ ├── DiffUtilCallbacks │ │ │ │ ├── Payloads │ │ │ │ │ ├── PadPayload.kt │ │ │ │ │ ├── ServerPayload.kt │ │ │ │ │ └── PadGroupPayload.kt │ │ │ │ └── PadAdapterDiffUtilCallback.kt │ │ │ ├── DragAndDropListener │ │ │ │ └── IDragAndDropListener.kt │ │ │ ├── ServerSelectionTracker │ │ │ │ ├── ServerKeyProvider.kt │ │ │ │ ├── ServerDetailsLookup.kt │ │ │ │ └── MakesServerSelectionTracker.kt │ │ │ ├── RecyclerViewKeyProvider.kt │ │ │ ├── PadSelectionTracker │ │ │ │ └── PadDetailsLookup.kt │ │ │ └── PadGroupSelectionTracker │ │ │ │ └── PadGroupDetailsLookup.kt │ │ │ ├── Database │ │ │ ├── PadGroupModel │ │ │ │ ├── PadGroupsWithPadlistByRelString.kt │ │ │ │ ├── PadGroupsAndPadListEntity.kt │ │ │ │ ├── PadGroupsWithPadList.kt │ │ │ │ ├── PadGroupEntity.kt │ │ │ │ ├── PadGroupRepository.kt │ │ │ │ ├── PadGroupViewModel.kt │ │ │ │ └── PadGroupDao.kt │ │ │ ├── TypeConverters │ │ │ │ └── DateConverter.kt │ │ │ ├── ServerModel │ │ │ │ ├── ServerRepository.kt │ │ │ │ ├── ServerDao.kt │ │ │ │ ├── ServerViewModel.kt │ │ │ │ └── ServerEntity.kt │ │ │ ├── PadModel │ │ │ │ ├── PadRepository.kt │ │ │ │ ├── PadDao.kt │ │ │ │ ├── PadViewModel.kt │ │ │ │ └── PadEntity.kt │ │ │ └── PadListDatabase.kt │ │ │ ├── Dialogs │ │ │ ├── Managers │ │ │ │ ├── ManagesDialog.kt │ │ │ │ ├── ManagesDeleteServerDialog.kt │ │ │ │ ├── ManagesDetectedPadDialog.kt │ │ │ │ ├── ManagesNewPadGroupDialog.kt │ │ │ │ ├── ManagesDeletePadGroupDialog.kt │ │ │ │ ├── ManagesDeletePadDialog.kt │ │ │ │ ├── ManagesPadViewAuthDialog.kt │ │ │ │ ├── ManagesSslErrorDialog.kt │ │ │ │ ├── ManagesNewServerDialog.kt │ │ │ │ ├── ManagesEditPadGroupDialog.kt │ │ │ │ ├── ManagesEditServerDialog.kt │ │ │ │ ├── ManagesGroupPadDialog.kt │ │ │ │ └── ManagesWhitelistServerDialog.kt │ │ │ ├── EditServerDialog.kt │ │ │ ├── NewPadGroupDialog.kt │ │ │ ├── ConfirmDialog.kt │ │ │ ├── EditPadGroupDialog.kt │ │ │ ├── PadViewAuthDialog.kt │ │ │ └── GroupPadDialog.kt │ │ │ └── Activities │ │ │ ├── AboutActivity.kt │ │ │ ├── InitialActivity.kt │ │ │ ├── IntroActivity.kt │ │ │ └── ServerListActivity.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── mikifus │ │ │ └── padland │ │ │ └── currentProject │ │ │ ├── dirs_bucket_0 │ │ │ └── graph.bin │ │ │ ├── dirs_bucket_1 │ │ │ └── graph.bin │ │ │ ├── jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_0 │ │ │ └── graph.bin │ │ │ └── jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_1 │ │ │ └── graph.bin │ └── androidTest │ │ └── java │ │ └── com │ │ └── mikifus │ │ └── padland │ │ ├── ApplicationTest.kt │ │ └── DbMigrationTest.kt └── proguard-rules.pro ├── metadata └── en-US │ ├── title.txt │ ├── images │ ├── icon.png │ ├── featureGraphic.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── full_description.txt │ ├── short_description.txt │ └── changelogs │ └── 26.txt ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── settings.gradle ├── pom.xml ├── gradle.properties ├── .gitignore └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /metadata/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Padland 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/padlandlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/drawable/padlandlogo.png -------------------------------------------------------------------------------- /metadata/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/intro_image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/drawable/intro_image1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/intro_image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/drawable/intro_image2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/intro_image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/drawable/intro_image3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/intro_image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/drawable/intro_image4.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/6.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/7.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/8.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/metadata/en-US/images/phoneScreenshots/9.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-large/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Padland is a tool to manage, share, remember and read collaborative documents based on the Etherpad technology in Android. 2 | -------------------------------------------------------------------------------- /metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Padland is a tool to manage, share, remember and read collaborative documents based on the Etherpad technology in Android. 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/values-v21/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /app/src/test/java/com/mikifus/padland/currentProject/dirs_bucket_0/graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/test/java/com/mikifus/padland/currentProject/dirs_bucket_0/graph.bin -------------------------------------------------------------------------------- /app/src/test/java/com/mikifus/padland/currentProject/dirs_bucket_1/graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/test/java/com/mikifus/padland/currentProject/dirs_bucket_1/graph.bin -------------------------------------------------------------------------------- /metadata/en-US/changelogs/26.txt: -------------------------------------------------------------------------------- 1 | - Migrated database to Room. 2 | - Better implementation of Material Design. 3 | - Extreme refactoring keeping the same features and adding some minor ones. 4 | - Export and import data. 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/PadlandApp.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland 2 | 3 | import android.app.Application 4 | 5 | /** 6 | * Parent App class 7 | * @author mikifus 8 | */ 9 | class PadlandApp : Application() {} -------------------------------------------------------------------------------- /app/src/main/res/color/appintro_desc_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/color/appintro_title_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Export/Maps/IGenericMap.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Export.Maps 2 | 3 | interface IGenericMap { 4 | val app: String 5 | val className: String 6 | val version: Double 7 | } -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 18 13:36:46 CEST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 128dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 0dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/ActionModes/AnyActionModeActive.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.ActionModes 2 | 3 | interface IAnyActionModeActive { 4 | var isAnyActionModeActive: Boolean 5 | } 6 | 7 | class AnyActionModeActive: IAnyActionModeActive { 8 | override var isAnyActionModeActive: Boolean = false 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/DiffUtilCallbacks/Payloads/PadPayload.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.DiffUtilCallbacks.Payloads 2 | 3 | sealed interface PadPayload { 4 | 5 | data class NameUrl(val name: String, val url: String) : PadPayload 6 | 7 | data class Url(val url: String) : PadPayload 8 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/mikifus/padland/currentProject/jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_0/graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/test/java/com/mikifus/padland/currentProject/jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_0/graph.bin -------------------------------------------------------------------------------- /app/src/test/java/com/mikifus/padland/currentProject/jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_1/graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikifus/padland/HEAD/app/src/test/java/com/mikifus/padland/currentProject/jar_6ebe4aa0f137964dd5af84c356b5ca53d2ca425847000b05280b625f24a72633_bucket_1/graph.bin -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/DiffUtilCallbacks/Payloads/ServerPayload.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.DiffUtilCallbacks.Payloads 2 | 3 | sealed interface ServerPayload { 4 | 5 | data class NameUrl(val name: String, val url: String) : ServerPayload 6 | 7 | data class Url(val url: String) : ServerPayload 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/group_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_group_button_up.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_group_button_down.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupsWithPadlistByRelString.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | data class PadGroupsWithPadlistByRelString( 4 | val mPadRelString: String = "", 5 | val mPadGroupRelString: String = "" 6 | 7 | ) { 8 | constructor() : this( 9 | "", "" 10 | ) 11 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mikifus/padland/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland; 2 | 3 | /** 4 | * Testing Fundamentals 5 | */ 6 | //public class ApplicationTest extends ApplicationTestCase { 7 | // public ApplicationTest() { 8 | // super(Application.class); 9 | // } 10 | //} -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_item.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/DiffUtilCallbacks/Payloads/PadGroupPayload.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.DiffUtilCallbacks.Payloads 2 | 3 | import com.mikifus.padland.Database.PadModel.Pad 4 | 5 | sealed interface PadGroupPayload { 6 | 7 | data class Title(val title: String) : PadGroupPayload 8 | 9 | data class TitlePadList(val title: String, val padList: List) : PadGroupPayload 10 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | google() 6 | } 7 | resolutionStrategy { 8 | eachPlugin { 9 | if (requested.id.id == "com.android.application") { 10 | useModule("com.android.tools.build:gradle:${requested.version}") 11 | } 12 | } 13 | } 14 | } 15 | 16 | include ':app' -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-v35/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v26/background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/padgroup_action_mode_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/TypeConverters/DateConverter.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.TypeConverters 2 | 3 | import androidx.room.TypeConverter 4 | import java.sql.Date 5 | 6 | object DateConverter { 7 | @TypeConverter 8 | fun toDate(dateLong: Long?): Date? { 9 | return dateLong?.let { Date(it) } 10 | } 11 | 12 | @TypeConverter 13 | fun fromDate(date: Date?): Long? { 14 | return date?.time 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_group_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | PadlandProject 9 | 1.0-SNAPSHOT 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_document.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/content_requests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | content://com.mikifus.padland.padlandcontentprovider/padlist 4 | content://com.mikifus.padland.padlandcontentprovider/padgroups 5 | content://com.mikifus.padland.padlandcontentprovider/padlist_padgroup_id/ 6 | -------------------------------------------------------------------------------- /app/src/main/res/menu/pad_list.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_document_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_content_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/DragAndDropListener/IDragAndDropListener.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.DragAndDropListener 2 | 3 | import android.view.DragEvent 4 | import android.view.View 5 | 6 | interface IDragAndDropListener { 7 | // fun setEmptyListTop(visibility: Boolean) 8 | // fun setEmptyListBottom(visibility: Boolean) 9 | 10 | fun onEnteredView(view: View, event: DragEvent) 11 | 12 | fun onExitedView(view: View, event: DragEvent) 13 | 14 | fun notifyChange(padGroupId: Long, padId: Long, position: Int) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/ServerSelectionTracker/ServerKeyProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.ServerSelectionTracker 2 | 3 | import androidx.recyclerview.selection.ItemKeyProvider 4 | import com.mikifus.padland.Adapters.ServerAdapter 5 | 6 | class ServerKeyProvider(private val adapter: ServerAdapter) : ItemKeyProvider(SCOPE_CACHED) { 7 | 8 | override fun getKey(position: Int): Long = 9 | adapter.data[position].mId 10 | 11 | override fun getPosition(key: Long): Int = 12 | adapter.data.indexOfFirst { it.mId == key } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/header_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dashed_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Export/ExclusionStrategies/IgnoreEntityIdStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Export.ExclusionStrategies 2 | 3 | import com.google.gson.ExclusionStrategy 4 | import com.google.gson.FieldAttributes 5 | import com.mikifus.padland.Database.PadModel.Pad 6 | 7 | 8 | class IgnoreEntityIdStrategy: ExclusionStrategy { 9 | override fun shouldSkipField(field: FieldAttributes): Boolean { 10 | // TODO: Search for PKEY in any class requested 11 | return field.name == Pad.PKEY 12 | } 13 | 14 | override fun shouldSkipClass(clazz: Class<*>?): Boolean { 15 | return false 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/server_action_mode_menu.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/RecyclerViewKeyProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters 2 | 3 | import androidx.recyclerview.selection.ItemKeyProvider 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class RecyclerViewKeyProvider(private val recyclerView: RecyclerView) : ItemKeyProvider(SCOPE_CACHED) { 7 | 8 | override fun getKey(position: Int): Long = 9 | recyclerView.adapter?.getItemId(position) 10 | ?: throw IllegalStateException("RecyclerView adapter is not set!") 11 | 12 | override fun getPosition(key: Long): Int { 13 | val viewHolder = recyclerView.findViewHolderForItemId(key) 14 | return viewHolder?.layoutPosition ?: RecyclerView.NO_POSITION 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Export/Maps/DatabaseMap.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Export.Maps 2 | 3 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 4 | import com.mikifus.padland.Database.PadGroupModel.PadGroupsWithPadlistByRelString 5 | import com.mikifus.padland.Database.PadModel.Pad 6 | import com.mikifus.padland.Database.ServerModel.Server 7 | 8 | class DatabaseMap( 9 | override val app: String, 10 | override val className: String, 11 | override val version: Double, 12 | val padland_servers: List? = null, 13 | val padgroups: List? = null, 14 | val padlist: List? = null, 15 | val padlist_padgroups: List? = null 16 | ): IGenericMap -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/mikifus/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -dontwarn javax.lang.model.element.Modifier -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | android.enableJetifier=true 14 | android.nonFinalResIds=false 15 | android.nonTransitiveRClass=false 16 | android.useAndroidX=true 17 | org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 18 | org.gradle.unsafe.configuration-cache= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Mikifus custom ### 2 | 3 | #Kubuntu 4 | .directory 5 | 6 | ### Found at http://stackoverflow.com/questions/16736856/what-should-be-in-my-gitignore-for-an-android-studio-project ### 7 | 8 | #built application files 9 | #*.apk 10 | #*.ap_ 11 | 12 | # files for the dex VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # generated files 19 | bin/ 20 | gen/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | /local.properties 24 | /key.properties 25 | 26 | # Windows thumbnail db 27 | Thumbs.db 28 | 29 | # OSX files 30 | .DS_Store 31 | 32 | # Eclipse project files 33 | .classpath 34 | .project 35 | 36 | # Android Studio 37 | .idea 38 | .gradle 39 | /build 40 | *.iml 41 | app/libs/ 42 | app/src/main/res/html_asset/ 43 | app/src/main/res/layout-v21/ 44 | release 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/PadSelectionTracker/PadDetailsLookup.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.PadSelectionTracker 2 | 3 | import android.view.MotionEvent 4 | import androidx.recyclerview.selection.ItemDetailsLookup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.mikifus.padland.Adapters.PadAdapter 7 | 8 | class PadDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() { 9 | 10 | //2 11 | override fun getItemDetails(event: MotionEvent): ItemDetails? { 12 | //3 13 | val view = recyclerView.findChildViewUnder(event.x, event.y) 14 | if (view != null) { 15 | //4 16 | return (recyclerView.getChildViewHolder(view) as PadAdapter.PadViewHolder).getItem() 17 | } 18 | return null 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/ServerSelectionTracker/ServerDetailsLookup.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.ServerSelectionTracker 2 | 3 | import android.view.MotionEvent 4 | import androidx.recyclerview.selection.ItemDetailsLookup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.mikifus.padland.Adapters.ServerAdapter 7 | 8 | class ServerDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() { 9 | 10 | //2 11 | override fun getItemDetails(event: MotionEvent): ItemDetails? { 12 | //3 13 | val view = recyclerView.findChildViewUnder(event.x, event.y) 14 | if (view != null) { 15 | //4 16 | return (recyclerView.getChildViewHolder(view) as ServerAdapter.ServerViewHolder).getItem() 17 | } 18 | return null 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/pad_action_mode_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/PadShareHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils; 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.core.app.ShareCompat 6 | import com.mikifus.padland.R 7 | 8 | class PadShareHelper { 9 | 10 | companion object { 11 | fun share(activity: AppCompatActivity, subject: String, urls: List) { 12 | val text = if(urls.size > 1) { 13 | urls.joinToString("\n") 14 | } else { 15 | urls[0] 16 | } 17 | ShareCompat.IntentBuilder(activity) 18 | .setType("text/plain") 19 | .setChooserTitle(activity.getText(R.string.share_document)) 20 | .setSubject(subject) 21 | .setText(text) 22 | .startChooser() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Export/TypeAdapters/SqlDateTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Export.TypeAdapters 2 | 3 | import com.google.gson.JsonDeserializationContext 4 | import com.google.gson.JsonDeserializer 5 | import com.google.gson.JsonElement 6 | import com.google.gson.JsonPrimitive 7 | import com.google.gson.JsonSerializationContext 8 | import com.google.gson.JsonSerializer 9 | import java.lang.reflect.Type 10 | import java.sql.Date 11 | 12 | object SqlDateTypeAdapter : JsonDeserializer, JsonSerializer { 13 | override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Date { 14 | return Date.valueOf(json.asString) 15 | } 16 | 17 | override fun serialize(src: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { 18 | return JsonPrimitive(src.toString()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/PadGroupSelectionTracker/PadGroupDetailsLookup.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.PadGroupSelectionTracker 2 | 3 | import android.view.MotionEvent 4 | import androidx.recyclerview.selection.ItemDetailsLookup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.mikifus.padland.Adapters.PadAdapter 7 | import com.mikifus.padland.Adapters.PadGroupAdapter 8 | 9 | class PadGroupDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() { 10 | 11 | //2 12 | override fun getItemDetails(event: MotionEvent): ItemDetails? { 13 | //3 14 | val view = recyclerView.findChildViewUnder(event.x, event.y) 15 | if (view != null) { 16 | //4 17 | return (recyclerView.getChildViewHolder(view) as PadGroupAdapter.PadGroupViewHolder).getItem() 18 | } 19 | return null 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/pad_info.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/ServerModel/ServerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.ServerModel 2 | 3 | import androidx.lifecycle.LiveData 4 | 5 | class ServerRepository(private val serverDao: ServerDao) { 6 | 7 | val getAll: LiveData> = serverDao.getAll() 8 | val getAllEnabled: LiveData> = serverDao.getAllEnabled() 9 | 10 | suspend fun insertServer(server: Server) { 11 | serverDao.insertAll(server) 12 | } 13 | 14 | fun insertServers(servers: List): List { 15 | return serverDao.insertAll(*servers.toTypedArray()) 16 | } 17 | 18 | suspend fun getById(id: Long): Server { 19 | return serverDao.getById(id) 20 | } 21 | 22 | fun updateServer(server: Server) { 23 | serverDao.update(server) 24 | } 25 | 26 | suspend fun deleteServer(server: Server) { 27 | return serverDao.delete(server) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/pad_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/PadLandWebViewClient/PadLandWebClientCallbacks.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.PadLandWebViewClient 2 | 3 | import android.graphics.Bitmap 4 | import android.webkit.HttpAuthHandler 5 | import android.webkit.SslErrorHandler 6 | import android.webkit.WebView 7 | 8 | interface PadLandWebClientCallbacks { 9 | 10 | fun onPageStartedCallback(view: WebView?, url: String?, favicon: Bitmap?){} 11 | fun onPageFinishedCallback(view: WebView?, url: String?){} 12 | fun onStartLoading(){} 13 | fun onStopLoading(){} 14 | suspend fun onUnsafeUrlProtocol(url: String): Boolean 15 | fun onReceivedSslError(handler: SslErrorHandler, url: String, message: String) 16 | 17 | /** 18 | * Implemented with runBlocking so it can suspend and 19 | * ask the user before returning. 20 | */ 21 | suspend fun onExternalHostUrlLoad(url: String): Boolean 22 | fun onReceivedHttpAuthRequestCallback(view: WebView, handler: HttpAuthHandler, host: String, realm: String) 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupsAndPadListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import com.mikifus.padland.Database.PadModel.Pad 7 | 8 | @Entity( 9 | tableName = PadGroupsAndPadList.TABLE_NAME, 10 | primaryKeys = ["_id_group", "_id_pad"], 11 | foreignKeys = [ 12 | ForeignKey( 13 | entity = PadGroup::class, 14 | parentColumns = ["_id"], 15 | childColumns = ["_id_group"], 16 | ), 17 | ForeignKey( 18 | entity = Pad::class, 19 | parentColumns = ["_id"], 20 | childColumns = ["_id_pad"], 21 | ) 22 | ] 23 | ) 24 | data class PadGroupsAndPadList ( 25 | @ColumnInfo(name = "_id_group") val mGroupId: Long, 26 | @ColumnInfo(name = "_id_pad") val mPadId: Long, 27 | ) { 28 | companion object { 29 | const val TABLE_NAME = "padlist_padgroups" 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/DiffUtilCallbacks/PadAdapterDiffUtilCallback.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.DiffUtilCallbacks 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.mikifus.padland.Adapters.DiffUtilCallbacks.Payloads.PadGroupPayload 5 | import com.mikifus.padland.Database.PadModel.Pad 6 | 7 | open class PadAdapterDiffUtilCallback( 8 | private val oldValue: List, 9 | private val newValue: List) 10 | : DiffUtil.Callback() { 11 | override fun getOldListSize(): Int = oldValue.size 12 | override fun getNewListSize(): Int = newValue.size 13 | 14 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 15 | return oldValue[oldItemPosition].mId == newValue[newItemPosition].mId 16 | } 17 | 18 | override fun areContentsTheSame( 19 | oldItemPosition: Int, 20 | newItemPosition: Int 21 | ): Boolean { 22 | return !oldValue[oldItemPosition].isPartiallyDifferentFrom(newValue[newItemPosition]) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.DialogFragment 6 | import androidx.fragment.app.FragmentTransaction 7 | 8 | abstract class ManagesDialog { 9 | abstract val dialog: DialogFragment 10 | abstract val DIALOG_TAG: String 11 | 12 | fun showDialog(activity: AppCompatActivity, transitionView: View? = null) { 13 | if(dialog.isAdded || activity.supportFragmentManager.isDestroyed) { 14 | return 15 | } 16 | 17 | activity.supportFragmentManager.beginTransaction().apply { 18 | add(android.R.id.content, dialog, DIALOG_TAG) 19 | addToBackStack(null) 20 | commit() 21 | } 22 | } 23 | 24 | fun closeDialog(activity: AppCompatActivity) { 25 | activity.supportFragmentManager.beginTransaction().apply { 26 | remove(dialog) 27 | commit() 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/PadClipboardHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import androidx.appcompat.app.AppCompatActivity 7 | 8 | 9 | class PadClipboardHelper { 10 | 11 | companion object { 12 | fun copyToClipboard(activity: AppCompatActivity, urls: List) { 13 | val text = if(urls.size > 1) { 14 | urls.joinToString("\n") 15 | } else { 16 | urls[0] 17 | } 18 | 19 | val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 20 | val clip = ClipData.newPlainText("URLs", text) 21 | 22 | clipboard.setPrimaryClip(clip) 23 | } 24 | 25 | fun getFromClipboard(activity: AppCompatActivity): String { 26 | val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 27 | return clipboard.primaryClip?.getItemAt(0)?.text?.toString() ?: "" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/header_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_pad_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Activities/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Activities 2 | 3 | import android.os.Bundle 4 | import android.text.method.LinkMovementMethod 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.appcompat.app.AppCompatActivity 8 | import com.mikifus.padland.R 9 | 10 | /** 11 | * Just displays an about message 12 | * @author 13 | */ 14 | class AboutActivity : AppCompatActivity() { 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_about) 18 | setSupportActionBar(findViewById(R.id.activity_toolbar)) 19 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 20 | 21 | // text2 has links specified by putting tags in the string 22 | // resource. By default these links will appear but not 23 | // respond to user input. To make them active, you need to 24 | // call setMovementMethod() on the TextView object. 25 | val textView = findViewById(R.id.textView) as TextView 26 | textView.movementMethod = LinkMovementMethod.getInstance() 27 | 28 | val textView1 = findViewById(R.id.textView2) as TextView 29 | textView1.movementMethod = LinkMovementMethod.getInstance() 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadModel/PadRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadModel 2 | 3 | import androidx.lifecycle.LiveData 4 | 5 | class PadRepository(private val padDao: PadDao) { 6 | 7 | val getAll: LiveData> = padDao.getAll() 8 | 9 | suspend fun insertPad(pad: Pad): Long { 10 | return padDao.insert(pad) 11 | } 12 | 13 | suspend fun insertPads(pads: List): List { 14 | return padDao.insertAll(pads) 15 | } 16 | 17 | // suspend fun getById(id: Long): LiveData { 18 | // return padDao.getById(id) 19 | // } 20 | suspend fun getById(id: Long): Pad { 21 | return padDao.getById(id) 22 | } 23 | 24 | suspend fun getByIds(ids: List): List { 25 | return padDao.getByIds(ids) 26 | } 27 | 28 | suspend fun getByUrl(url: String): Pad { 29 | return padDao.getByUrl(url) 30 | } 31 | 32 | suspend fun updatePadGroup(padGroup: Pad): Int { 33 | return padDao.update(padGroup) 34 | } 35 | 36 | // fun updatePadPosition(padId: Long, position: Int) { 37 | // return padDao.updatePadPosition(padId, position) 38 | // } 39 | 40 | suspend fun deletePad(pad: Pad): Int { 41 | return padDao.delete(pad) 42 | } 43 | 44 | suspend fun deletePads(pads: List): Int { 45 | return padDao.delete(pads) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupsWithPadList.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import androidx.room.Embedded 5 | import androidx.room.Entity 6 | import androidx.room.Junction 7 | import androidx.room.Relation 8 | import com.mikifus.padland.Database.PadModel.Pad 9 | 10 | @Entity 11 | data class PadGroupsWithPadList( 12 | @Embedded var padGroup: PadGroup, 13 | @Relation( 14 | entity = Pad::class, 15 | parentColumn = "_id", 16 | entityColumn = "_id", 17 | associateBy = Junction( 18 | value = PadGroupsAndPadList::class, 19 | parentColumn = "_id_group", 20 | entityColumn = "_id_pad" 21 | ) 22 | ) 23 | val padList: List 24 | ) { 25 | fun isPartiallyDifferentFrom(padGroupsWithPadList: PadGroupsWithPadList): Boolean { 26 | return padGroup.isPartiallyDifferentFrom(padGroupsWithPadList.padGroup) || 27 | // padGroup.isPartiallyDifferentFrom(padGroupsWithPadList.padGroup) 28 | padList.size != padGroupsWithPadList.padList.size || 29 | padList.withIndex().any { it -> 30 | it.value.isPartiallyDifferentFrom(padGroupsWithPadList.padList[it.index]) 31 | } 32 | // padList.any { //TODO: Use diffutil here? 33 | // it.isPartiallyDifferentFrom(padGroupsWithPadList.padList.indexOf()) 34 | // }) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/EditServerDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.text.Editable 4 | import com.mikifus.padland.R 5 | 6 | class EditServerDialog: NewServerDialog() { 7 | 8 | private var data: Map? = null 9 | 10 | override fun setFormData(data: HashMap) { 11 | isNew = false 12 | this.data = data 13 | applyFormData() 14 | } 15 | 16 | private fun applyFormData() { 17 | data?.get("name")?.let { 18 | mNameEditText?.text = Editable.Factory.getInstance().newEditable(it.toString()) 19 | } 20 | data?.get("url")?.let { 21 | mUrlEditText?.text = Editable.Factory.getInstance().newEditable(it.toString()) 22 | } 23 | data?.get("prefix")?.let { 24 | mPadPrefixEditText?.text = Editable.Factory.getInstance().newEditable(it.toString()) 25 | } 26 | data?.get("jquery")?.let { 27 | mJqueryCheckBox?.isChecked = it as Boolean 28 | if(it == false) { 29 | mLiteCheckbox?.isChecked = false 30 | } 31 | } 32 | data?.get("cryptpad")?.let { 33 | mCryptPadCheckbox?.isChecked = it as Boolean 34 | } 35 | } 36 | 37 | override fun initToolBar() { 38 | super.initToolBar() 39 | 40 | toolbar!!.title = getString(R.string.edit) 41 | } 42 | 43 | override fun onResume() { 44 | super.onResume() 45 | applyFormData() 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Activities/InitialActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Activities 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.PadListDatabase 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.launch 10 | 11 | class InitialActivity: AppCompatActivity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | 15 | // Force Room DB migration 16 | lifecycleScope.launch(Dispatchers.Main) { 17 | PadListDatabase.migrateBeforeRoom(this@InitialActivity) 18 | } 19 | 20 | launchNext() 21 | } 22 | 23 | private fun launchNext() { 24 | 25 | val userDetails = getSharedPreferences(packageName + "_preferences", 26 | MODE_PRIVATE 27 | ) 28 | if(userDetails.getBoolean(OPTION_FIRST_START, true)) { 29 | // Save first start 30 | userDetails.edit().putBoolean(OPTION_FIRST_START, false).apply() 31 | 32 | // Launch app intro 33 | val i = Intent(this, IntroActivity::class.java) 34 | startActivity(i) 35 | finish() 36 | } else { 37 | // Launch real main activity 38 | startActivity(Intent(this, PadListActivity::class.java)) 39 | finish() 40 | } 41 | } 42 | 43 | companion object { 44 | private const val OPTION_FIRST_START = "is_first_start" 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/ServerModel/ServerDao.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.ServerModel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Insert 7 | import androidx.room.Query 8 | import androidx.room.Update 9 | 10 | @Dao 11 | interface ServerDao { 12 | @Insert 13 | fun insertAll(vararg pads: Server): List 14 | 15 | @Update 16 | fun update(vararg pads: Server) 17 | 18 | @Delete 19 | fun delete(pad: Server) 20 | 21 | @Query("SELECT * FROM padland_servers") 22 | fun getAll(): LiveData> 23 | 24 | @Query("SELECT * FROM padland_servers WHERE enabled = 1") 25 | fun getAllEnabled(): LiveData> 26 | 27 | // @Query("SELECT * FROM padland_servers WHERE _id == :id") 28 | // fun getById(id: Long): LiveData 29 | 30 | @Query("SELECT * FROM padland_servers WHERE _id == :id") 31 | suspend fun getById(id: Long): Server 32 | 33 | @Query("SELECT padprefix FROM padland_servers WHERE url == :url") 34 | fun getServerPrefixFromUrl(url: String?): String 35 | 36 | // fun getServerPrefixFromUrl(context: Context?, server: String?): String? { 37 | // var c = 0 38 | // val serverUrlList = getServerUrlList(context) 39 | // val serverUrlPrefixList = getServerUrlPrefixList(context) 40 | // for (s in serverUrlList) { 41 | // if (s == server) { 42 | // break 43 | // } 44 | // c++ 45 | // } 46 | // return if (c < serverUrlPrefixList.size && serverUrlPrefixList[c] != null) { 47 | // serverUrlPrefixList[c] 48 | // } else null 49 | // } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadModel/PadDao.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadModel 2 | 3 | import android.database.Cursor 4 | import androidx.lifecycle.LiveData 5 | import androidx.room.Dao 6 | import androidx.room.Delete 7 | import androidx.room.Insert 8 | import androidx.room.Query 9 | import androidx.room.Update 10 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 11 | 12 | @Dao 13 | interface PadDao { 14 | @Insert 15 | suspend fun insert(pad: Pad): Long 16 | 17 | @Insert 18 | fun insertAll(pads: List): List 19 | 20 | @Update 21 | suspend fun update(vararg pads: Pad): Int 22 | 23 | @Delete 24 | fun delete(pad: Pad): Int 25 | 26 | @Delete 27 | suspend fun delete(pad: List): Int 28 | 29 | @Query("SELECT * FROM padlist") 30 | fun getAll(): LiveData> 31 | 32 | @Query("SELECT * FROM padlist") 33 | fun getAllCursor(): /*List*/ Cursor 34 | 35 | @Query("SELECT * FROM padlist WHERE _id == :id") 36 | suspend fun getById(id: Long): Pad 37 | 38 | @Query("SELECT * FROM padlist WHERE _id IN (:ids)") 39 | suspend fun getByIds(ids: List): List 40 | 41 | @Query("SELECT * FROM padlist WHERE _id == :id") 42 | fun getByIdCursor(id: Long): /*LiveData>*/Cursor 43 | 44 | @Query("SELECT * FROM padlist WHERE url == :url") 45 | suspend fun getByUrl(url: String): Pad 46 | 47 | @Query("SELECT * FROM padlist WHERE url == :url") 48 | fun getByUrlCursor(url: String): Cursor 49 | 50 | @Query("DELETE FROM padlist WHERE _id IN (:selectionArgs)") 51 | fun deleteBy(selectionArgs: Array?): Int 52 | 53 | // @Query("UPDATE padlist SET position = :position WHERE _id = :padId") 54 | // fun updatePadPosition(padId: Long, position: Int) 55 | } 56 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mikifus/padland/DbMigrationTest.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland 2 | 3 | import androidx.room.Room 4 | import androidx.room.migration.AutoMigrationSpec 5 | import androidx.room.testing.MigrationTestHelper 6 | import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory 7 | import androidx.test.ext.junit.runners.AndroidJUnit4 8 | import androidx.test.platform.app.InstrumentationRegistry 9 | import com.mikifus.padland.Database.Migrations.MIGRATION_BEFORE_ROOM 10 | import com.mikifus.padland.Database.PadListDatabase 11 | import org.junit.Rule 12 | import org.junit.Test 13 | import org.junit.runner.RunWith 14 | import java.io.IOException 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class DbMigrationTest { 18 | private val TEST_DB = "padlist" 19 | 20 | // Array of all migrations. 21 | private val ALL_MIGRATIONS = arrayOf(MIGRATION_BEFORE_ROOM) 22 | 23 | @get:Rule 24 | val helper: MigrationTestHelper = MigrationTestHelper( 25 | InstrumentationRegistry.getInstrumentation(), 26 | PadListDatabase::class.java, 27 | listOf(), 28 | FrameworkSQLiteOpenHelperFactory() 29 | ) 30 | 31 | @Test 32 | @Throws(IOException::class) 33 | fun migrateAll() { 34 | // Create earliest version of the database. 35 | helper.createDatabase(TEST_DB, 9).apply { 36 | close() 37 | } 38 | 39 | // Open latest version of the database. Room validates the schema 40 | // once all migrations execute. 41 | Room.databaseBuilder( 42 | InstrumentationRegistry.getInstrumentation().targetContext, 43 | PadListDatabase::class.java, 44 | TEST_DB 45 | ).addMigrations(*ALL_MIGRATIONS).build().apply { 46 | openHelper.writableDatabase.close() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 14 | 15 | 21 | 22 | 27 | 28 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/NewPadGroupDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.EditText 10 | import android.widget.Toast 11 | import com.mikifus.padland.R 12 | 13 | 14 | /** 15 | * Created by mikifus on 10/03/16. 16 | */ 17 | class NewPadGroupDialog: FormDialog() { 18 | 19 | private var mEditText: EditText? = null 20 | 21 | override fun validateForm(): Boolean { 22 | val name = mEditText!!.text.toString() 23 | 24 | if (name.isEmpty()) { 25 | Toast.makeText(context, getString(R.string.padlist_dialog_new_padgroup_invalid), Toast.LENGTH_LONG).show() 26 | return false 27 | } 28 | 29 | return true 30 | } 31 | 32 | override fun getFormData(): Map { 33 | val text = mEditText!!.text.toString() 34 | 35 | val data = HashMap() 36 | data["name"] = text 37 | 38 | return data 39 | } 40 | 41 | override fun clearForm() { 42 | mEditText!!.text = null 43 | } 44 | 45 | override fun onStart() { 46 | super.onStart() 47 | 48 | mEditText = requireView().findViewById(R.id.txt_padgroup_name) as EditText 49 | mEditText?.requestFocus() 50 | } 51 | 52 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 53 | super.onCreateView(inflater, container, savedInstanceState) 54 | 55 | return inflater.inflate(R.layout.dialog_new_padgroup, container, false) 56 | } 57 | 58 | override fun initToolBar() { 59 | super.initToolBar() 60 | 61 | toolbar!!.title = getString(R.string.padlist_dialog_new_padgroup_title) 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/ServerModel/ServerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.ServerModel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.mikifus.padland.Database.PadListDatabase 9 | import kotlinx.coroutines.Deferred 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.async 12 | import kotlinx.coroutines.launch 13 | import kotlinx.coroutines.withContext 14 | 15 | class ServerViewModel(application: Application): AndroidViewModel(application) { 16 | 17 | val getAll: LiveData> 18 | val getAllEnabled: LiveData> 19 | 20 | val server = MutableLiveData() 21 | 22 | private val repository: ServerRepository 23 | 24 | init { 25 | val serverDao = PadListDatabase.getInstance(application).serverDao() 26 | repository = ServerRepository(serverDao) 27 | getAll = repository.getAll 28 | getAllEnabled = repository.getAllEnabled 29 | } 30 | 31 | suspend fun insertServer(server: Server) { 32 | viewModelScope.launch(Dispatchers.IO) { 33 | repository.insertServer(server) 34 | } 35 | } 36 | 37 | suspend fun getById(id: Long): Server { 38 | return repository.getById(id) 39 | } 40 | 41 | suspend fun updateServer(server: Server) { 42 | viewModelScope.launch(Dispatchers.IO) { repository.updateServer(server) } 43 | } 44 | 45 | fun deleteServer(id: Long) { 46 | viewModelScope.launch { 47 | repository.deleteServer(Server.withOnlyId(id).value!!) 48 | } 49 | } 50 | 51 | fun deleteServer(ids: List)=viewModelScope.launch { 52 | viewModelScope.launch { 53 | ids.forEach { 54 | val server = Server.withOnlyId(it).value!! 55 | 56 | withContext(Dispatchers.IO) { 57 | repository.deleteServer(server) 58 | } 59 | } 60 | } 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_new_padgroup.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 22 | 23 | 28 | 29 | 34 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_group_pad.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 22 | 23 | 28 | 29 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesDeleteServerDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.ServerModel.ServerViewModel 8 | import com.mikifus.padland.Dialogs.ConfirmDialog 9 | import com.mikifus.padland.R 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | 13 | interface IManagesDeleteServerDialog { 14 | var serverViewModel: ServerViewModel? 15 | var ids: List 16 | fun showDeleteServerDialog(activity: AppCompatActivity, ids: List) 17 | } 18 | 19 | class ManagesDeleteServerDialog: ManagesDialog(), IManagesDeleteServerDialog { 20 | override val DIALOG_TAG: String = "DIALOG_DELETE_SERVER" 21 | 22 | override val dialog by lazy { ConfirmDialog() } 23 | override var serverViewModel: ServerViewModel? = null 24 | override var ids: List = listOf() 25 | 26 | override fun showDeleteServerDialog(activity: AppCompatActivity, ids: List) { 27 | initViewModels(activity) 28 | initEvents(activity) 29 | 30 | this.ids = ids 31 | 32 | dialog.setTitle(activity.getString(R.string.delete)) 33 | dialog.setMessage(activity.getString(R.string.serverlist_dialog_delete_sure_to_delete)) 34 | dialog.positiveButtonText = activity.getString(R.string.delete) 35 | 36 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 37 | } 38 | 39 | private fun initViewModels(activity: AppCompatActivity) { 40 | if(serverViewModel == null) { 41 | serverViewModel = ViewModelProvider(activity)[ServerViewModel::class.java] 42 | } 43 | } 44 | 45 | private fun initEvents(activity: AppCompatActivity) { 46 | dialog.positiveButtonCallback = DialogInterface.OnClickListener { dialog, which -> 47 | confirmDeleteServer(activity) 48 | this.ids = listOf() 49 | } 50 | } 51 | 52 | private fun confirmDeleteServer(activity: AppCompatActivity) { 53 | activity.lifecycleScope.launch(Dispatchers.IO) { 54 | serverViewModel!!.deleteServer(ids) 55 | } 56 | dialog.dismiss() 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import android.content.ContentValues 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.room.ColumnInfo 6 | import androidx.room.Entity 7 | import androidx.room.PrimaryKey 8 | import java.sql.Date 9 | 10 | @Entity(tableName = PadGroup.TABLE_NAME ) 11 | data class PadGroup( 12 | @PrimaryKey(autoGenerate = true) 13 | @ColumnInfo(name = "_id") val mId: Long, 14 | @ColumnInfo(name = "name") var mName: String, 15 | @ColumnInfo(name = "position", defaultValue = "0") val mPosition: Int, 16 | @ColumnInfo(name = "last_used_date", defaultValue = "(strftime('%s','now'))") val mLastUsedDate: Date, 17 | @ColumnInfo(name = "create_date", defaultValue = "(strftime('%s','now'))") val mCreateDate: Date, 18 | @ColumnInfo(name = "access_count", defaultValue = "0") val mAccessCount: Long, 19 | ) 20 | { 21 | constructor() : this( 22 | 0, 23 | "", 24 | 0, 25 | Date(System.currentTimeMillis()), 26 | Date(System.currentTimeMillis()), 27 | 0 28 | ) 29 | 30 | companion object { 31 | const val TABLE_NAME = "padgroups" 32 | 33 | fun withOnlyId(id: Long): MutableLiveData { 34 | return MutableLiveData(PadGroup().copy(mId = id)) 35 | } 36 | 37 | fun fromName(name: String): MutableLiveData { 38 | return MutableLiveData(PadGroup().copy(mName = name)) 39 | } 40 | 41 | fun fromContentValues(contentValues: ContentValues): MutableLiveData { 42 | val item = MutableLiveData(PadGroup()) 43 | 44 | // contentValues.valueSet().forEach { item.value = item.value?.copy(contentValues.getAsString(it.key)) } 45 | // item.value = item.value?.copy(mId=contentValues.getAsLong(Pad::mId::class.java.canonicalName)) 46 | if(contentValues.containsKey("name")) item.value = item.value!!.copy(mName = contentValues.getAsString("name")) 47 | 48 | 49 | return item 50 | } 51 | } 52 | 53 | fun isPartiallyDifferentFrom(padGroup: PadGroup): Boolean { 54 | return ( 55 | mName != padGroup.mName || 56 | mPosition != padGroup.mPosition 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesDetectedPadDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.mikifus.padland.Dialogs.ConfirmDialog 6 | import com.mikifus.padland.R 7 | 8 | interface IManagesDetectedPadDialog { 9 | fun showDetectedPadDialog(activity: AppCompatActivity, 10 | url: String, 11 | onCancelCallback: (() -> Unit)? = {}, 12 | onConfirmCallback: (() -> Unit)? = {}) 13 | } 14 | class ManagesDetectedPadDialog: ManagesDialog(), IManagesDetectedPadDialog { 15 | override val DIALOG_TAG: String = "DIALOG_SSL_ERROR" 16 | 17 | override val dialog by lazy { ConfirmDialog() } 18 | 19 | override fun showDetectedPadDialog(activity: AppCompatActivity, 20 | url: String, 21 | onCancelCallback: (() -> Unit)?, 22 | onConfirmCallback: (() -> Unit)?) { 23 | 24 | dialog.setTitle(activity.getString(R.string.padview_dialog_detected_pad)) 25 | dialog.setMessage(activity.getString(R.string.padview_dialog_detected_pad_desc)) 26 | dialog.positiveButtonText = activity.getString(R.string.save_pad) 27 | dialog.negativeButtonText = activity.getString(R.string.cancel) 28 | 29 | if(dialog.isAdded && !activity.supportFragmentManager.isDestroyed) { 30 | dialog.dismiss() 31 | } 32 | 33 | initEvents(activity, url, onCancelCallback, onConfirmCallback) 34 | 35 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 36 | } 37 | 38 | private fun initEvents(activity: AppCompatActivity, 39 | url: String, 40 | onCancelCallback: (() -> Unit)?, 41 | onConfirmCallback: (() -> Unit)?) { 42 | dialog.negativeButtonCallback = DialogInterface.OnClickListener { dialog, which -> 43 | onCancelCallback?.let { it() } 44 | } 45 | dialog.onDismissCallback = { 46 | onCancelCallback?.let { it() } 47 | } 48 | dialog.positiveButtonCallback = DialogInterface.OnClickListener { dialog, which -> 49 | onConfirmCallback?.let { it() } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/ConfirmDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.content.DialogInterface.OnClickListener 6 | import android.os.Bundle 7 | import androidx.fragment.app.DialogFragment 8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 9 | import com.mikifus.padland.R 10 | 11 | class ConfirmDialog: DialogFragment() { 12 | private var title: String = "" 13 | private var icon: Int? = null 14 | private var message: String = "" 15 | var positiveButtonText: String? = null 16 | var positiveButtonCallback: OnClickListener? = null 17 | var negativeButtonText: String? = null 18 | var negativeButtonCallback: OnClickListener? = OnClickListener { dialog, which -> dialog.dismiss() } 19 | var neutralButtonText: String? = null 20 | var neutralButtonCallback: OnClickListener? = null 21 | var onDismissCallback: (() -> Unit)? = null 22 | 23 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 24 | if(positiveButtonText == null) { 25 | positiveButtonText = getString(R.string.ok) 26 | } 27 | if(negativeButtonText == null) { 28 | negativeButtonText = getString(R.string.cancel) 29 | } 30 | 31 | val builder = MaterialAlertDialogBuilder(requireContext()) 32 | .setTitle(title) 33 | .setMessage(message) 34 | .setPositiveButton(positiveButtonText, positiveButtonCallback) 35 | 36 | if(neutralButtonText !== null) { 37 | builder.setNeutralButton(neutralButtonText, neutralButtonCallback) 38 | } 39 | if(negativeButtonText !== null) { 40 | builder.setNegativeButton(negativeButtonText, negativeButtonCallback) 41 | } 42 | 43 | if(icon != null) { 44 | builder.setIcon(icon!!) 45 | } 46 | 47 | return builder.create() 48 | } 49 | 50 | override fun onDismiss(dialog: DialogInterface) { 51 | super.onDismiss(dialog) 52 | onDismissCallback?.let { it() } 53 | } 54 | 55 | fun setTitle(title: String) { 56 | this.title = title 57 | } 58 | 59 | fun setIcon(icon: Int) { 60 | this.icon = icon 61 | } 62 | 63 | fun setMessage(message: String) { 64 | this.message = message 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesNewPadGroupDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers; 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 8 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 9 | import com.mikifus.padland.Dialogs.NewPadGroupDialog 10 | import com.mikifus.padland.R 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | 14 | interface IManagesNewPadGroupDialog { 15 | var padGroupViewModel: PadGroupViewModel? 16 | fun showNewPadGroupDialog(activity: AppCompatActivity, 17 | animationOriginView: View? = null) 18 | } 19 | 20 | public class ManagesNewPadGroupDialog: ManagesDialog(), IManagesNewPadGroupDialog { 21 | override val DIALOG_TAG: String = "DIALOG_NEW_PADGROUP" 22 | 23 | override val dialog by lazy { NewPadGroupDialog() } 24 | override var padGroupViewModel: PadGroupViewModel? = null 25 | 26 | override fun showNewPadGroupDialog(activity: AppCompatActivity, 27 | animationOriginView: View?) { 28 | showDialog(activity) 29 | initViewModels(activity) 30 | initEvents(activity) 31 | initAnimations(animationOriginView) 32 | } 33 | 34 | private fun initViewModels(activity: AppCompatActivity) { 35 | if(padGroupViewModel == null) { 36 | padGroupViewModel = ViewModelProvider(activity)[PadGroupViewModel::class.java] 37 | } 38 | } 39 | 40 | private fun initEvents(activity: AppCompatActivity) { 41 | dialog.setPositiveButtonCallback { data -> 42 | saveNewPadgroupDialog(activity, data["name"].toString()) 43 | dialog.clearForm() 44 | closeDialog(activity) 45 | } 46 | } 47 | 48 | private fun initAnimations(animationOriginView: View?) { 49 | dialog.animationOriginView = animationOriginView 50 | } 51 | 52 | private fun saveNewPadgroupDialog(activity: AppCompatActivity, name: String) { 53 | val padGroup = PadGroup.fromName(name).value!! 54 | activity.lifecycleScope.launch(Dispatchers.IO) { 55 | padGroupViewModel!!.insertPadGroup(padGroup) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/server_list_recyclerview_item_server.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 25 | 26 | 35 | 36 | 43 | 44 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/EditPadGroupDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.os.Bundle 6 | import android.text.Editable 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.EditText 11 | import android.widget.Toast 12 | import com.mikifus.padland.R 13 | 14 | 15 | /** 16 | * Created by mikifus on 10/03/16. 17 | */ 18 | class EditPadGroupDialog: FormDialog() { 19 | 20 | private var mEditText: EditText? = null 21 | private var data: Map = mapOf() 22 | 23 | override fun validateForm(): Boolean { 24 | val text = mEditText!!.text.toString() 25 | 26 | if(!NAME_VALIDATION.matcher(text).matches()) { 27 | Toast.makeText(context, getString(R.string.padlist_dialog_new_padgroup_invalid), Toast.LENGTH_LONG).show() 28 | return false 29 | } 30 | 31 | return true 32 | } 33 | 34 | override fun setFormData(data: HashMap) { 35 | this.data = data 36 | applyFormData() 37 | } 38 | 39 | private fun applyFormData() { 40 | data["name"]?.let { 41 | mEditText!!.text = Editable.Factory.getInstance().newEditable(it.toString()) 42 | } 43 | } 44 | 45 | override fun getFormData(): Map { 46 | val text = mEditText!!.text.toString() 47 | 48 | val data = HashMap() 49 | data["name"] = text 50 | 51 | return data 52 | } 53 | 54 | override fun clearForm() { 55 | mEditText!!.text = null 56 | } 57 | 58 | override fun onStart() { 59 | super.onStart() 60 | 61 | mEditText = requireView().findViewById(R.id.txt_padgroup_name) as EditText 62 | mEditText?.requestFocus() 63 | } 64 | 65 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 66 | super.onCreateView(inflater, container, savedInstanceState) 67 | 68 | return inflater.inflate(R.layout.dialog_new_padgroup, container, false) 69 | } 70 | 71 | override fun initToolBar() { 72 | super.initToolBar() 73 | 74 | toolbar!!.title = getString(R.string.edit) 75 | } 76 | 77 | override fun onResume() { 78 | super.onResume() 79 | applyFormData() 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesDeletePadGroupDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 8 | import com.mikifus.padland.Dialogs.ConfirmDialog 9 | import com.mikifus.padland.R 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | 13 | interface IManagesDeletePadGroupDialog { 14 | var padGroupViewModel: PadGroupViewModel? 15 | var ids: List 16 | fun showDeletePadGroupDialog(activity: AppCompatActivity, ids: List) 17 | } 18 | 19 | class ManagesDeletePadGroupDialog: ManagesDialog(), IManagesDeletePadGroupDialog { 20 | override val DIALOG_TAG: String = "DIALOG_DELETE_PADGROUP" 21 | 22 | override val dialog by lazy { ConfirmDialog() } 23 | override var padGroupViewModel: PadGroupViewModel? = null 24 | override var ids: List = listOf() 25 | 26 | override fun showDeletePadGroupDialog(activity: AppCompatActivity, ids: List) { 27 | initViewModels(activity) 28 | initEvents(activity) 29 | 30 | this.ids = ids 31 | 32 | dialog.setTitle(activity.getString(R.string.delete)) 33 | dialog.setMessage(activity.getString(R.string.sure_to_delete_group)) 34 | dialog.positiveButtonText = activity.getString(R.string.delete) 35 | 36 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 37 | } 38 | 39 | private fun initViewModels(activity: AppCompatActivity) { 40 | if(padGroupViewModel == null) { 41 | padGroupViewModel = ViewModelProvider(activity)[PadGroupViewModel::class.java] 42 | } 43 | } 44 | 45 | private fun initEvents(activity: AppCompatActivity) { 46 | dialog.positiveButtonCallback = DialogInterface.OnClickListener { dialog, which -> 47 | confirmDeletePadGroupDialog(activity) 48 | this.ids = listOf() 49 | } 50 | } 51 | 52 | private fun confirmDeletePadGroupDialog(activity: AppCompatActivity) { 53 | activity.lifecycleScope.launch(Dispatchers.IO) { 54 | ids.forEach { 55 | padGroupViewModel!!.deletePadGroupsAndPadListByPadGroupId(it) 56 | padGroupViewModel!!.deletePadGroups(it) 57 | } 58 | } 59 | dialog.dismiss() 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.mikifus.padland.Database.PadModel.Pad 5 | 6 | class PadGroupRepository(private val padGroupDao: PadGroupDao) { 7 | 8 | val getAll: LiveData> = padGroupDao.getAll() 9 | val getPadGroupsWithPadList: LiveData> = 10 | padGroupDao.getPadGroupsWithPadList() 11 | val getPadsWithoutGroup: LiveData> = padGroupDao.getPadsWithoutGroup() 12 | val getAllPadGroupsWithPadlistRelString: LiveData> = 13 | padGroupDao.getAllPadGroupsWithPadlistRelString() 14 | 15 | fun insertPadGroup(padGroup: PadGroup) { 16 | padGroupDao.insertAll(padGroup) 17 | } 18 | 19 | fun insertPadGroups(padGroups: List): List { 20 | return padGroupDao.insertAll(*padGroups.toTypedArray()) 21 | } 22 | 23 | suspend fun getById(id: Long): PadGroup { 24 | return padGroupDao.getById(id) 25 | } 26 | 27 | suspend fun getByPadId(id: Long): PadGroup { 28 | return padGroupDao.getByPadId(id) 29 | } 30 | 31 | fun getPadGroupsAndPadListByPadIds(padIds: List): List { 32 | return padGroupDao.getPadGroupsAndPadListByPadIds(padIds) 33 | } 34 | 35 | fun updatePadGroup(padGroup: PadGroup) { 36 | padGroupDao.update(padGroup) 37 | } 38 | 39 | suspend fun deletePadGroup(padGroup: PadGroup) { 40 | return padGroupDao.delete(padGroup) 41 | } 42 | 43 | suspend fun deletePadGroups(padGroups: List) { 44 | return padGroupDao.delete(padGroups) 45 | } 46 | 47 | suspend fun insertPadGroupWithPadlist(padGroupsAndPadList: PadGroupsAndPadList) { 48 | padGroupDao.insertPadGroupWithPadlist(padGroupsAndPadList) 49 | } 50 | 51 | fun deletePadGroupsAndPadList(padId: Long) { 52 | padGroupDao.deletePadGroupsAndPadList(padId) 53 | } 54 | 55 | fun deletePadGroupsAndPadListByPadGroupId(padId: Long): Int { 56 | return padGroupDao.deletePadGroupsAndPadListByPadGroupId(padId) 57 | } 58 | 59 | fun insertPadGroupWithPadlistByRelString(relations: List): List { 60 | val result = mutableListOf() 61 | relations.forEach { 62 | result.add( 63 | padGroupDao.insertPadGroupWithPadlistByRelString(it.mPadGroupRelString, it.mPadRelString) 64 | ) 65 | } 66 | return result 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Views/Helpers/ResizeableNestedScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Views.Helpers 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.ViewGroup 6 | import androidx.coordinatorlayout.widget.CoordinatorLayout 7 | import androidx.core.view.children 8 | import androidx.core.widget.NestedScrollView 9 | import com.google.android.material.behavior.HideBottomViewOnScrollBehavior 10 | import com.mikifus.padland.R 11 | 12 | /** 13 | * The coordinator layout hides and shows FABs dynamically on 14 | * scroll, but when the size of the NestedScrollView contents 15 | * change, no scroll event is emitted and no scroll can be 16 | * done by the user. This means the FABs will stay hidden 17 | * without option to show. 18 | * 19 | * This class listens for layout changes and detects when 20 | * the contents of the NestedScrollView's single child 21 | * are smaller than the scroll view in order to search 22 | * for FABs to show. 23 | * 24 | * Combine it with some space in the child to avoid hiding 25 | * the content behind the buttons (padding won't do right). 26 | * Example: 27 | * 30 | */ 31 | class ResizeableNestedScrollView @JvmOverloads constructor( 32 | context: Context, 33 | attributeSet: AttributeSet? = null, 34 | defStyleAttr: Int = R.attr.nestedScrollViewStyle) 35 | : NestedScrollView(context, attributeSet, defStyleAttr) { 36 | 37 | init { 38 | addOnLayoutChangeListener { 39 | view, 40 | left, top, right, bottom, 41 | oldLeft, oldTop, oldRight, oldBottom-> 42 | 43 | val nestedChild = (view as NestedScrollView).children.iterator().next() 44 | if(nestedChild.bottom < bottom - 1) { 45 | val it = (parent as ViewGroup).children.iterator() 46 | while(it.hasNext()) { 47 | val child = it.next() 48 | if(child.layoutParams 49 | is CoordinatorLayout.LayoutParams && 50 | (child.layoutParams as CoordinatorLayout.LayoutParams).behavior 51 | is HideBottomViewOnScrollBehavior 52 | ) { 53 | ((child.layoutParams as CoordinatorLayout.LayoutParams).behavior 54 | as HideBottomViewOnScrollBehavior).slideUp(child, true) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/WhiteListMatcher.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils 2 | 3 | import android.net.Uri 4 | import android.text.TextUtils 5 | import java.net.MalformedURLException 6 | import java.net.URL 7 | 8 | /** 9 | * Created by mikifus on 19/07/16. 10 | */ 11 | object WhiteListMatcher { 12 | /** 13 | * Performs a wildcard matching for the text and pattern 14 | * provided. 15 | * 16 | * @param compareText the text to be tested for matches. 17 | * 18 | * @param pattern the pattern to be matched for. 19 | * This can contain the wildcard character '*' (asterisk). 20 | * 21 | * @return true if a match is found, false 22 | * otherwise. 23 | * 24 | * @url http://www.adarshr.com/simple-implementation-of-wildcard-text-matching-using-java 25 | */ 26 | private fun wildCardMatch(compareText: String, pattern: String?): Boolean { 27 | // Create the cards by splitting using a RegEx. If more speed 28 | // is desired, a simpler character based splitting can be done. 29 | var text = compareText 30 | val cards = pattern!!.split("\\*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 31 | 32 | // Iterate over the cards. 33 | for (card in cards) { 34 | val idx = text.indexOf(card) 35 | 36 | // Card not detected in the text. 37 | if (idx == -1) { 38 | return false 39 | } 40 | 41 | // Move ahead, towards the right of the text. 42 | text = text.substring(idx + card.length) 43 | } 44 | return true 45 | } 46 | 47 | /** 48 | * Checks if the url parameter has a valid host 49 | * among a list of hosts. 50 | * 51 | * @param url 52 | * @param hostsWhitelist 53 | * @return 54 | */ 55 | fun isValidHost(url: String?, hostsWhitelist: List): Boolean { 56 | if (!TextUtils.isEmpty(url)) { 57 | val host = Uri.parse(url).host ?: return false 58 | for (whitelistedHost in hostsWhitelist) { 59 | if (wildCardMatch(host, whitelistedHost)) { 60 | return true 61 | } 62 | } 63 | } 64 | return false 65 | } 66 | 67 | /** 68 | * Checks if the URL can be parsed. 69 | * 70 | * @param padUrl 71 | * @return 72 | */ 73 | fun checkValidUrl(padUrl: String?): Boolean { 74 | // Check if it is a valid url 75 | try { 76 | URL(padUrl) 77 | } catch (e: MalformedURLException) { 78 | e.printStackTrace() 79 | return false 80 | } 81 | return true 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesDeletePadDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface.OnClickListener 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 8 | import com.mikifus.padland.Database.PadModel.PadViewModel 9 | import com.mikifus.padland.Dialogs.ConfirmDialog 10 | import com.mikifus.padland.R 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | 14 | interface IManagesDeletePadDialog { 15 | var padGroupViewModel: PadGroupViewModel? 16 | var padViewModel: PadViewModel? 17 | var ids: List 18 | fun showDeletePadDialog(activity: AppCompatActivity, ids: List) 19 | } 20 | 21 | class ManagesDeletePadDialog: ManagesDialog(), IManagesDeletePadDialog { 22 | override val DIALOG_TAG: String = "DIALOG_DELETE_PAD" 23 | 24 | override val dialog by lazy { ConfirmDialog() } 25 | 26 | override var padGroupViewModel: PadGroupViewModel? = null 27 | override var padViewModel: PadViewModel? = null 28 | override var ids: List = listOf() 29 | 30 | override fun showDeletePadDialog(activity: AppCompatActivity, ids: List) { 31 | initViewModels(activity) 32 | initEvents(activity) 33 | 34 | this.ids = ids 35 | 36 | dialog.setTitle(activity.getString(R.string.delete)) 37 | dialog.setMessage(activity.getString(R.string.sure_to_delete_pad)) 38 | dialog.positiveButtonText = activity.getString(R.string.delete) 39 | 40 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 41 | } 42 | 43 | private fun initViewModels(activity: AppCompatActivity) { 44 | if(padViewModel == null) { 45 | padViewModel = ViewModelProvider(activity)[PadViewModel::class.java] 46 | } 47 | if(padGroupViewModel == null) { 48 | padGroupViewModel = ViewModelProvider(activity)[PadGroupViewModel::class.java] 49 | } 50 | } 51 | 52 | private fun initEvents(activity: AppCompatActivity) { 53 | dialog.positiveButtonCallback = OnClickListener { dialog, which -> 54 | confirmDeletePadDialog(activity) 55 | this.ids = listOf() 56 | } 57 | } 58 | 59 | private fun confirmDeletePadDialog(activity: AppCompatActivity) { 60 | activity.lifecycleScope.launch(Dispatchers.IO) { 61 | ids.forEach { 62 | padGroupViewModel!!.deletePadGroupsAndPadList(it) 63 | padViewModel!!.deletePad(it) 64 | } 65 | } 66 | dialog.dismiss() 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesPadViewAuthDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.os.Build 4 | import android.webkit.CookieManager 5 | import android.webkit.HttpAuthHandler 6 | import android.webkit.URLUtil 7 | import android.webkit.WebView 8 | import androidx.appcompat.app.AppCompatActivity 9 | import com.mikifus.padland.Dialogs.PadViewAuthDialog 10 | 11 | interface IManagesPadViewAuthDialog { 12 | fun showPadViewAuthDialog( 13 | activity: AppCompatActivity, 14 | view: WebView, 15 | handler: HttpAuthHandler 16 | ) 17 | } 18 | class ManagesPadViewAuthDialog: ManagesDialog(), IManagesPadViewAuthDialog { 19 | override val DIALOG_TAG: String = "DIALOG_BASIC_AUTH" 20 | 21 | override val dialog by lazy { PadViewAuthDialog() } 22 | private var webView: WebView? = null 23 | private var lastLoginUrl: String? = null 24 | 25 | override fun showPadViewAuthDialog(activity: AppCompatActivity, view: WebView, handler: HttpAuthHandler) { 26 | this.webView = view 27 | showDialog(activity) 28 | initEvents(activity, handler) 29 | } 30 | 31 | private fun initEvents(activity: AppCompatActivity, handler: HttpAuthHandler) { 32 | dialog.setPositiveButtonCallback { data -> 33 | handler.proceed(data["user"].toString(), data["password"].toString()) 34 | webView?.clearCache(true) // No effect? 35 | dialog.clearForm() 36 | closeDialog(activity) 37 | } 38 | dialog.setNegativeButtonCallback { 39 | handler.cancel() 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 41 | CookieManager.getInstance().removeAllCookies(null) 42 | } else { 43 | @Suppress("DEPRECATION") 44 | CookieManager.getInstance().removeAllCookie() 45 | } 46 | } 47 | dialog.setOnResumeCallback { 48 | if(lastLoginUrl == webView?.url) { 49 | // Credentials must be invalid, trying again 50 | dialog.showLoginError() 51 | } 52 | lastLoginUrl = webView?.url 53 | 54 | if(URLUtil.isHttpUrl(webView?.url)) { 55 | // Warn the user that they're not using SSL 56 | dialog.showSslWarning() 57 | } 58 | } 59 | dialog.dismissCallback = { 60 | handler.cancel() 61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 62 | CookieManager.getInstance().removeAllCookies(null) 63 | } else { 64 | @Suppress("DEPRECATION") 65 | CookieManager.getInstance().removeAllCookie() 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadModel/PadViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadModel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.mikifus.padland.Database.PadListDatabase 9 | import kotlinx.coroutines.Deferred 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.async 12 | 13 | class PadViewModel(application: Application): AndroidViewModel(application) { 14 | 15 | val getAll: LiveData> 16 | 17 | val pad: MutableLiveData = MutableLiveData() 18 | 19 | private val repository: PadRepository 20 | 21 | init { 22 | val padDao = PadListDatabase.getInstance(application).padDao() 23 | repository = PadRepository(padDao) 24 | getAll = repository.getAll 25 | } 26 | 27 | suspend fun insertPad(pad: Pad): Long { 28 | val deferred: Deferred = viewModelScope.async(Dispatchers.IO) { 29 | repository.insertPad(pad) 30 | } 31 | 32 | return deferred.await() 33 | } 34 | 35 | suspend fun insertPads(pads: List): List { 36 | val deferred: Deferred> = viewModelScope.async(Dispatchers.IO) { 37 | repository.insertPads(pads) 38 | } 39 | 40 | return deferred.await() 41 | } 42 | 43 | suspend fun getById(id: Long): Pad { 44 | val padQuery = repository.getById(id) 45 | pad.postValue(padQuery) 46 | return padQuery 47 | } 48 | 49 | suspend fun getByIds(ids: List): List { 50 | return repository.getByIds(ids) 51 | } 52 | 53 | suspend fun getByUrl(url: String): Pad { 54 | return repository.getByUrl(url) 55 | } 56 | 57 | suspend fun updatePad(pad: Pad): Int { 58 | val result = repository.updatePadGroup(pad) 59 | if(result > 0 && this.pad.value != null && pad.mId == this.pad.value!!.mId) { 60 | getById(pad.mId) 61 | } 62 | return result 63 | } 64 | 65 | // fun updatePadPosition(padId: Long, position: Int) { 66 | // viewModelScope.launch(Dispatchers.IO) { repository.updatePadPosition(padId, position) } 67 | // } 68 | 69 | suspend fun deletePad(id: Long): Int { 70 | val pad = Pad.withOnlyId(id).value!! 71 | val result = repository.deletePad(pad) 72 | if(result > 0 && this.pad.value != null && pad.mId == this.pad.value!!.mId) { 73 | this.pad.postValue(null) 74 | } 75 | return result 76 | } 77 | 78 | suspend fun deletePads(ids: List): Int { 79 | val pads = ids.map { Pad.withOnlyId(it).value!! } 80 | return repository.deletePads(pads) 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesSslErrorDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import android.net.Uri 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.mikifus.padland.Dialogs.ConfirmDialog 8 | import com.mikifus.padland.R 9 | 10 | interface IManagesSslErrorDialog { 11 | fun showSslErrorDialog(activity: AppCompatActivity, 12 | url: String, 13 | error: String, 14 | onCancelCallback: (() -> Unit)? = {}, 15 | onIgnoreCallback: (() -> Unit)? = {}) 16 | } 17 | class ManagesSslErrorDialog: ManagesDialog(), IManagesSslErrorDialog { 18 | override val DIALOG_TAG: String = "DIALOG_SSL_ERROR" 19 | 20 | override val dialog by lazy { ConfirmDialog() } 21 | 22 | override fun showSslErrorDialog(activity: AppCompatActivity, 23 | url: String, 24 | error: String, 25 | onCancelCallback: (() -> Unit)?, 26 | onIgnoreCallback: (() -> Unit)?) { 27 | 28 | dialog.setTitle(activity.getString(R.string.ssl_error)) 29 | dialog.setMessage(error) 30 | dialog.positiveButtonText = activity.getString(R.string.ignore) 31 | dialog.neutralButtonText = activity.getString(R.string.whitelist_server_dialog_open_browser) 32 | 33 | if(dialog.isAdded && !activity.supportFragmentManager.isDestroyed) { 34 | dialog.dismiss() 35 | } 36 | 37 | initEvents(activity, url, onCancelCallback, onIgnoreCallback) 38 | 39 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 40 | } 41 | 42 | private fun initEvents(activity: AppCompatActivity, 43 | url: String, 44 | onCancelCallback: (() -> Unit)?, 45 | onIgnoreCallback: (() -> Unit)?) { 46 | dialog.negativeButtonCallback = DialogInterface.OnClickListener { dialog, which -> 47 | onCancelCallback?.let { it() } 48 | } 49 | dialog.positiveButtonCallback = DialogInterface.OnClickListener { dialog, which -> 50 | onIgnoreCallback?.let { it() } 51 | } 52 | dialog.neutralButtonCallback = DialogInterface.OnClickListener { dialog, which -> 53 | confirmBrowser(activity, url) 54 | } 55 | dialog.onDismissCallback = { 56 | onIgnoreCallback?.let { it() } 57 | } 58 | } 59 | 60 | private fun confirmBrowser(activity: AppCompatActivity, url: String) { 61 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) 62 | activity.startActivity(intent) 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_server_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 18 | 19 | 23 | 24 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 54 | 55 | 56 | 57 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Views/Helpers/SpinnerHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Views.Helpers 2 | 3 | import android.content.Context 4 | import android.text.InputType 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import android.widget.AdapterView 8 | import android.widget.AdapterView.OnItemSelectedListener 9 | import com.google.android.material.textfield.MaterialAutoCompleteTextView 10 | import com.mikifus.padland.R 11 | 12 | 13 | /** 14 | * Material Spinners do not exist, an autocomplete view must be used. 15 | * This is highly problematic for a basic feature. 16 | * 17 | * This class eases a bit the trouble, yet it is far from perfect. 18 | * 19 | * WARNING: setAdapter() will only work properly if called in onResume() 20 | * 21 | * @see https://rmirabelle.medium.com/there-is-no-material-design-spinner-for-android-3261b7c77da8 22 | */ 23 | class SpinnerHelper @JvmOverloads constructor(context: Context, 24 | attributeSet: AttributeSet? = null, 25 | defStyleAttr: Int = R.attr.autoCompleteTextViewStyle) 26 | : MaterialAutoCompleteTextView(context, attributeSet, defStyleAttr) { 27 | 28 | var helperOnItemSelectedListener: OnItemSelectedListener? = null 29 | var selectedItemPosition: Int = 0 30 | /** 31 | * On setting this the input will display 32 | * the text for the selected position. 33 | */ 34 | set(value) { 35 | if(value < 0) { 36 | return 37 | } 38 | adapter?.getItem(value)?.let { 39 | if(text.toString() != it.toString()) { 40 | setText(it.toString(), false) 41 | } 42 | } 43 | field = value 44 | } 45 | 46 | init { 47 | setOnItemClickListener { _, _, position, _ -> 48 | selectedItemPosition = position 49 | } 50 | setOnItemSelectedListener(object: OnItemSelectedListener { 51 | override fun onItemSelected(adapter: AdapterView<*>?, view: View?, pos: Int, p3: Long) { 52 | helperOnItemSelectedListener?.onItemSelected(adapter, view, pos, p3) 53 | } 54 | 55 | override fun onNothingSelected(p0: AdapterView<*>?) { 56 | helperOnItemSelectedListener?.onNothingSelected(p0) 57 | } 58 | }) 59 | 60 | // This doesn't work until a second click, idk why. 61 | // Yes, I tried setting an OnClickListener that would 62 | // also not fire until the second click. And I tried 63 | // an OnTouchListener too. 64 | inputType = InputType.TYPE_NULL 65 | 66 | // This is terrible but the only way to overcome the a bug 67 | // that shows the keyboard on click. No workaround, no 68 | // real solution, it happens for autocomplete views in 69 | // dialogs. 70 | isFocusable = false 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/PadViewAuthDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Button 10 | import android.widget.EditText 11 | import android.widget.Toast 12 | import com.google.android.material.textview.MaterialTextView 13 | import com.mikifus.padland.R 14 | 15 | 16 | /** 17 | * Created by mikifus on 10/03/16. 18 | */ 19 | class PadViewAuthDialog: FormDialog() { 20 | 21 | private var mUserEditText: EditText? = null 22 | private var mPasswordEditText: EditText? = null 23 | private var mAuthErrorMessage: MaterialTextView? = null 24 | private var mAuthSslMessage: MaterialTextView? = null 25 | 26 | override fun validateForm(): Boolean { return true } 27 | 28 | override fun getFormData(): Map { 29 | val user = mUserEditText!!.text.toString() 30 | val password = mPasswordEditText!!.text.toString() 31 | 32 | val data = HashMap() 33 | data["user"] = user 34 | data["password"] = password 35 | 36 | return data 37 | } 38 | 39 | override fun clearForm() { 40 | mUserEditText!!.text = null 41 | // mPasswordEditText!!.text = null 42 | hideLoginError() 43 | hideSslWarning() 44 | } 45 | 46 | override fun onStart() { 47 | super.onStart() 48 | 49 | mUserEditText = requireView().findViewById(R.id.txt_username) 50 | mPasswordEditText = requireView().findViewById(R.id.txt_password) 51 | mAuthErrorMessage = requireView().findViewById(R.id.auth_error_message) 52 | mAuthSslMessage = requireView().findViewById(R.id.auth_warning_message) 53 | 54 | mUserEditText?.requestFocus() 55 | } 56 | 57 | override fun initToolBar() { 58 | super.initToolBar() 59 | 60 | toolbar!!.title = getString(R.string.padview_dialog_basicatuh_title) 61 | } 62 | 63 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 64 | super.onCreateView(inflater, container, savedInstanceState) 65 | return inflater.inflate(R.layout.dialog_auth, container, false) 66 | } 67 | 68 | override fun initEvents() { 69 | val positiveButton = toolbar?.findViewById(R.id.dialog_positive_button) 70 | positiveButton?.setOnClickListener(positiveButtonCallback) 71 | } 72 | fun showLoginError() { 73 | mAuthErrorMessage?.visibility = View.VISIBLE 74 | } 75 | fun hideLoginError() { 76 | mAuthErrorMessage?.visibility = View.GONE 77 | } 78 | fun showSslWarning() { 79 | mAuthSslMessage?.visibility = View.VISIBLE 80 | } 81 | fun hideSslWarning() { 82 | mAuthSslMessage?.visibility = View.GONE 83 | } 84 | 85 | override fun getTheme(): Int = R.style.DialogStyleMinWidth 86 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.mikifus.padland.Database.PadListDatabase 9 | import com.mikifus.padland.Database.PadModel.Pad 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.withContext 13 | 14 | class PadGroupViewModel(application: Application): AndroidViewModel(application) { 15 | 16 | val getAll: LiveData> 17 | val getPadGroupsWithPadList: LiveData> 18 | val getPadsWithoutGroup: LiveData> 19 | 20 | val padGroup = MutableLiveData() 21 | 22 | private val repository: PadGroupRepository 23 | 24 | init { 25 | val padGroupDao = PadListDatabase.getInstance(application).padGroupDao() 26 | repository = PadGroupRepository(padGroupDao) 27 | getAll = repository.getAll 28 | getPadGroupsWithPadList = repository.getPadGroupsWithPadList 29 | getPadsWithoutGroup = repository.getPadsWithoutGroup 30 | } 31 | 32 | suspend fun insertPadGroup(padGroup: PadGroup) { 33 | viewModelScope.launch(Dispatchers.IO) { 34 | repository.insertPadGroup(padGroup) 35 | } 36 | } 37 | 38 | suspend fun getById(id: Long): PadGroup { 39 | return repository.getById(id) 40 | } 41 | 42 | suspend fun updatePadGroup(padGroup: PadGroup) { 43 | viewModelScope.launch(Dispatchers.IO) { repository.updatePadGroup(padGroup) } 44 | } 45 | 46 | suspend fun deletePadGroups(id: Long) { 47 | val padGroup = withContext(Dispatchers.Main) { 48 | PadGroup.withOnlyId(id).value!! 49 | } 50 | repository.deletePadGroup(padGroup) 51 | } 52 | 53 | suspend fun deletePadGroups(ids: List) { 54 | val padGroups: List = withContext(Dispatchers.Main) { 55 | ids.map { PadGroup.withOnlyId(it).value!! } 56 | } 57 | repository.deletePadGroups(padGroups) 58 | } 59 | 60 | suspend fun getByPadId(id: Long): PadGroup { 61 | return repository.getByPadId(id) 62 | } 63 | 64 | suspend fun getPadGroupsAndPadListByPadIds(padIds: List): List { 65 | return repository.getPadGroupsAndPadListByPadIds(padIds) 66 | } 67 | 68 | suspend fun insertPadGroupsAndPadList(padGroupsAndPadList: PadGroupsAndPadList) { 69 | repository.insertPadGroupWithPadlist(padGroupsAndPadList) 70 | } 71 | 72 | suspend fun deletePadGroupsAndPadList(padId: Long) { 73 | repository.deletePadGroupsAndPadList(padId) 74 | } 75 | 76 | suspend fun deletePadGroupsAndPadListByPadGroupId(padGroupId: Long): Int { 77 | return repository.deletePadGroupsAndPadListByPadGroupId(padGroupId) 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/ServerModel/ServerEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.ServerModel 2 | 3 | import android.content.ContentValues 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.room.ColumnInfo 6 | import androidx.room.Entity 7 | import androidx.room.PrimaryKey 8 | 9 | @Entity(tableName = "padland_servers") 10 | data class Server( 11 | @PrimaryKey(autoGenerate = true) 12 | @ColumnInfo(name = "_id") val mId: Long, 13 | @ColumnInfo(name = "name") val mName: String, 14 | @ColumnInfo(name = "url") val mUrl: String, 15 | @ColumnInfo(name = "padprefix") val mPadprefix: String, 16 | @ColumnInfo(name = "position", defaultValue = "0") val mPosition: Long, 17 | @ColumnInfo(name = "jquery", defaultValue = "0") val mJquery: Boolean, 18 | @ColumnInfo(name = "cryptpad", defaultValue = "0") val mCryptPad: Boolean, 19 | @ColumnInfo(name = "enabled", defaultValue = "1") val mEnabled: Boolean, 20 | // @ColumnInfo(name = "create_date", defaultValue = "(strftime('%s','now'))") val mCreateDate: Date 21 | ) 22 | { 23 | constructor() : this( 24 | 0, 25 | "", 26 | "", 27 | "", 28 | 0, 29 | true, 30 | true, 31 | true, 32 | // Date(System.currentTimeMillis()) 33 | ) 34 | 35 | companion object { 36 | const val TABLE_NAME = "padland_servers" 37 | 38 | fun fromFormContentValues(contentValues: ContentValues): MutableLiveData { 39 | val item = MutableLiveData(Server()) 40 | 41 | // contentValues.valueSet().forEach { item.value = item.value?.copy(contentValues.getAsString(it.key)) } 42 | // item.value = item.value?.copy(mId=contentValues.getAsLong(Pad::mId::class.java.canonicalName)) 43 | if(contentValues.containsKey("name")) item.value = item.value!!.copy(mName = contentValues.getAsString("name")) 44 | if(contentValues.containsKey("prefix")) item.value = item.value!!.copy(mPadprefix = contentValues.getAsString("uprefix")) 45 | if(contentValues.containsKey("url")) item.value = item.value!!.copy(mUrl = contentValues.getAsString("url")) 46 | if(contentValues.containsKey("jquery")) item.value = item.value!!.copy(mJquery = contentValues.getAsBoolean("jquery")) 47 | if(contentValues.containsKey("cryptpad")) item.value = item.value!!.copy(mCryptPad = contentValues.getAsBoolean("cryptpad")) 48 | if(contentValues.containsKey("enabled")) item.value = item.value!!.copy(mEnabled = contentValues.getAsBoolean("enabled")) 49 | 50 | return item 51 | } 52 | 53 | fun withOnlyId(id: Long): MutableLiveData { 54 | val item = MutableLiveData(Server()) 55 | 56 | item.value = item.value!!.copy(mId = id) 57 | 58 | return item 59 | } 60 | } 61 | 62 | fun isPartiallyDifferentFrom(server: Server): Boolean { 63 | return ( 64 | mName != server.mName || 65 | mUrl != server.mUrl || 66 | mPosition != server.mPosition 67 | ) 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/CryptPad/CryptPadUtils.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.CryptPad 2 | 3 | class CryptPadUtils { 4 | companion object { 5 | private val CRYPTPAD_PAD_URL = Regex("https?://[^/]+/[^/]+/#/\\d+/[^/]+/[^/]+/[^/]+/") 6 | private val CRYPTPAD_PAD_URL_V2 = Regex("https?://[^/]+/[^/]+/#/1/[^/]+/[^/]+/[^/]+/") 7 | private val CRYPTPAD_PAD_URL_V3 = Regex("https?://[^/]+/[^/]+/#/3/[^/]+/[^/]+/[^/]+/") 8 | private val CRYPTPAD_REPLACE_TYPE_NAME_REGEX = Regex("(\\d)/[a-z]+(/?)") 9 | private const val CRYPTPAD_REPLACE_TYPE_NAME_REGEX_SUBSTITUTION = "$1/__type__$2" 10 | private val CRYPTPAD_REPLACE_TYPE_PATH_REGEX = Regex("(/?)[a-z]+/#/(\\d)/[a-z]+(/?)") 11 | private const val CRYPTPAD_REPLACE_TYPE_PATH_REGEX_SUBSTITUTION = "$1__type__/#/$2/__type__$3" 12 | private val CRYPTPAD_PAD_URL_NEW_REGEX = Regex("(https?://[^/]+/)([a-z]+)/#.*") 13 | private const val CRYPTPAD_PAD_URL_NEW_REGEX_SUBSTITUTION = "$1$2" 14 | private val CRYPTPAD_DRIVE_URL = Regex("(https?://[^/]+/)[^/]*") 15 | private const val CRYPTPAD_DRIVE_URL_SUBSTITUTION = "$1drive" 16 | 17 | fun replaceCryptPadType(nameOrUrl: String, type: String): String { 18 | var typeString = type 19 | val typeFormatIndex = type.indexOf("/#") 20 | if (typeFormatIndex > -1) { 21 | typeString = typeString.substring(1, typeFormatIndex) 22 | } 23 | val isName = CRYPTPAD_REPLACE_TYPE_NAME_REGEX.containsMatchIn(nameOrUrl) 24 | val regex = if (isName) CRYPTPAD_REPLACE_TYPE_NAME_REGEX else CRYPTPAD_REPLACE_TYPE_PATH_REGEX 25 | val substitution = if (isName) CRYPTPAD_REPLACE_TYPE_NAME_REGEX_SUBSTITUTION else CRYPTPAD_REPLACE_TYPE_PATH_REGEX_SUBSTITUTION 26 | return nameOrUrl.replace(regex, substitution.replace("__type__", typeString)) 27 | } 28 | 29 | fun makePadPrefixFromServerAndType(server: String, type: String): String { 30 | var serverString = server 31 | var typeString = type 32 | if (serverString.endsWith("/")) { 33 | serverString = serverString.substringBeforeLast("/") 34 | } 35 | if (!typeString.endsWith("/#/")) { 36 | typeString = "/$type/#/" 37 | 38 | } 39 | return serverString + typeString 40 | } 41 | 42 | fun seemsCrpytPadUrl(url: String, version: Number? = null): Boolean { 43 | return when (version) { 44 | 2 -> CRYPTPAD_PAD_URL_V2.matches(url) 45 | 3 -> CRYPTPAD_PAD_URL_V3.matches(url) 46 | else -> CRYPTPAD_PAD_URL.matches(url) 47 | } 48 | } 49 | 50 | fun applyNewPadUrl(url: String): String { 51 | return url.replace( 52 | CRYPTPAD_PAD_URL_NEW_REGEX, 53 | CRYPTPAD_PAD_URL_NEW_REGEX_SUBSTITUTION 54 | ) 55 | } 56 | 57 | fun applyDriveUrl(url: String): String { 58 | return url.replace( 59 | CRYPTPAD_DRIVE_URL, 60 | CRYPTPAD_DRIVE_URL_SUBSTITUTION 61 | ) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Intro/LinkableAppIntroFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Intro 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.text.method.LinkMovementMethod 6 | import android.text.util.Linkify 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.TextView 11 | import androidx.annotation.ColorInt 12 | import androidx.annotation.ColorRes 13 | import androidx.annotation.DrawableRes 14 | import androidx.annotation.FontRes 15 | import androidx.core.content.ContextCompat 16 | import androidx.fragment.app.FragmentActivity 17 | import com.github.appintro.AppIntroBaseFragment 18 | import com.github.appintro.AppIntroFragment 19 | import com.github.appintro.SlideBackgroundColorHolder 20 | import com.github.appintro.model.SliderPage 21 | import com.mikifus.padland.R 22 | 23 | 24 | class LinkableAppIntroFragment(override val layoutId: Int) : AppIntroBaseFragment() { 25 | 26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 27 | super.onViewCreated(view, savedInstanceState) 28 | 29 | view.findViewById(R.id.description).movementMethod = LinkMovementMethod.getInstance() 30 | } 31 | 32 | companion object { 33 | @JvmOverloads 34 | @JvmStatic 35 | fun createInstance( 36 | title: CharSequence? = null, 37 | description: CharSequence? = null, 38 | @DrawableRes imageDrawable: Int = 0, 39 | @ColorRes backgroundColorRes: Int = 0, 40 | @ColorRes titleColorRes: Int = 0, 41 | @ColorRes descriptionColorRes: Int = 0, 42 | @FontRes titleTypefaceFontRes: Int = 0, 43 | @FontRes descriptionTypefaceFontRes: Int = 0, 44 | @DrawableRes backgroundDrawable: Int = 0 45 | ) : LinkableAppIntroFragment { 46 | return createInstance( 47 | SliderPage( 48 | title = title, 49 | description = description, 50 | imageDrawable = imageDrawable, 51 | backgroundColorRes = backgroundColorRes, 52 | titleColorRes = titleColorRes, 53 | descriptionColorRes = descriptionColorRes, 54 | titleTypefaceFontRes = titleTypefaceFontRes, 55 | descriptionTypefaceFontRes = descriptionTypefaceFontRes, 56 | backgroundDrawable = backgroundDrawable 57 | ) 58 | ) 59 | } 60 | 61 | /** 62 | * Generates an [AppIntroFragment] from a given [SliderPage] 63 | * 64 | * @param sliderPage the [SliderPage] object which contains all attributes for 65 | * the current slide 66 | * 67 | * @return An [AppIntroFragment] created instance 68 | */ 69 | @JvmStatic 70 | fun createInstance(sliderPage: SliderPage): LinkableAppIntroFragment { 71 | val slide = LinkableAppIntroFragment(com.github.appintro.R.layout.appintro_fragment_intro) 72 | slide.arguments = sliderPage.toBundle() 73 | return slide 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadGroupModel/PadGroupDao.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadGroupModel 2 | 3 | import android.database.Cursor 4 | import androidx.lifecycle.LiveData 5 | import androidx.room.Dao 6 | import androidx.room.Delete 7 | import androidx.room.Insert 8 | import androidx.room.OnConflictStrategy 9 | import androidx.room.Query 10 | import androidx.room.Update 11 | import com.mikifus.padland.Database.PadModel.Pad 12 | 13 | 14 | @Dao 15 | interface PadGroupDao { 16 | @Insert 17 | fun insertAll(vararg padGroups: PadGroup): List 18 | 19 | @Update 20 | fun update(vararg padGroups: PadGroup) 21 | 22 | @Delete 23 | suspend fun delete(padGroup: PadGroup) 24 | 25 | @Delete 26 | suspend fun delete(padGroups: List) 27 | 28 | @Query("SELECT * FROM padgroups") 29 | fun getAll(): LiveData> 30 | 31 | @Query("SELECT * FROM padgroups") 32 | fun getAllCursor(): Cursor 33 | 34 | @Query("SELECT * FROM padgroups WHERE _id == :id") 35 | suspend fun getById(id: Long): PadGroup 36 | 37 | @Query("SELECT * FROM padgroups WHERE _id == :id") 38 | fun getByIdCursor(id: Long): Cursor 39 | 40 | @Query("DELETE FROM padgroups WHERE _id IN (:selectionArgs)") 41 | fun deleteBy(selectionArgs: Array?): Int 42 | 43 | @Query("SELECT * FROM padgroups") 44 | fun getPadGroupsWithPadList(): LiveData> 45 | 46 | @Query("SELECT * FROM padlist_padgroups WHERE _id_pad IN (:padIds)") 47 | fun getPadGroupsAndPadListByPadIds(padIds: List): List 48 | 49 | @Insert(onConflict = OnConflictStrategy.REPLACE) 50 | suspend fun insertPadGroupWithPadlist(padGroupsAndPadList: PadGroupsAndPadList): Long 51 | 52 | @Query("DELETE FROM padlist_padgroups WHERE _id_pad = :padId") 53 | fun deletePadGroupsAndPadList(padId: Long) 54 | 55 | @Query("DELETE FROM padlist_padgroups WHERE _id_group = :padId") 56 | fun deletePadGroupsAndPadListByPadGroupId(padId: Long): Int 57 | 58 | @Query("SELECT * FROM padlist WHERE _id NOT IN" + 59 | "(SELECT _id_pad FROM padlist_padgroups)") 60 | fun getPadsWithoutGroup(): LiveData> 61 | 62 | @Query("SELECT * FROM padgroups WHERE _id IN" + 63 | "(SELECT _id_group FROM padlist_padgroups WHERE _id_pad = :id)") 64 | suspend fun getByPadId(id: Long): PadGroup 65 | 66 | @Query("SELECT padlist.url as mPadRelString, padgroups.name as mPadGroupRelString" + 67 | " FROM padlist_padgroups" + 68 | " JOIN padlist ON padlist._id=padlist_padgroups._id_pad" + 69 | " JOIN padgroups ON padgroups._id=padlist_padgroups._id_group") 70 | fun getAllPadGroupsWithPadlistRelString(): LiveData> 71 | 72 | @Query("INSERT OR REPLACE INTO padlist_padgroups (_id_group, _id_pad) " + 73 | " SELECT DISTINCT padgroups._id, padlist._id" + 74 | " FROM padgroups" + 75 | " JOIN padlist ON padlist.url=:mPadRelString" + 76 | " WHERE padgroups.name=:mPadGroupRelString" 77 | ) 78 | fun insertPadGroupWithPadlistByRelString(mPadGroupRelString: String, mPadRelString: String): Long 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesNewServerDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.lifecycleScope 6 | import com.mikifus.padland.Database.ServerModel.Server 7 | import com.mikifus.padland.Database.ServerModel.ServerViewModel 8 | import com.mikifus.padland.Dialogs.NewServerDialog 9 | import com.mikifus.padland.R 10 | import com.mikifus.padland.Utils.PadServer 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | 14 | interface IManagesNewServerDialog { 15 | var serverViewModel: ServerViewModel? 16 | fun showNewServerDialog(activity: AppCompatActivity, 17 | url: String? = null, 18 | onDismissCallBack: (() -> Unit)? = null) 19 | } 20 | 21 | class ManagesNewServerDialog: ManagesDialog(), IManagesNewServerDialog { 22 | override val DIALOG_TAG: String = "DIALOG_NEW_SERVER" 23 | 24 | override val dialog by lazy { NewServerDialog() } 25 | override var serverViewModel: ServerViewModel? = null 26 | 27 | override fun showNewServerDialog(activity: AppCompatActivity, 28 | url: String?, 29 | onDismissCallBack: (() -> Unit)?) { 30 | showDialog(activity) 31 | initViewModels(activity) 32 | initEvents(activity, onDismissCallBack) 33 | initAnimations(activity) 34 | 35 | url?.let { onSetInitialUrl(url) } 36 | } 37 | 38 | private fun initViewModels(activity: AppCompatActivity) { 39 | if(serverViewModel == null) { 40 | serverViewModel = ViewModelProvider(activity)[ServerViewModel::class.java] 41 | } 42 | } 43 | 44 | private fun initEvents(activity: AppCompatActivity, 45 | onDismissCallBack: (() -> Unit)?) { 46 | dialog.setPositiveButtonCallback { data -> 47 | saveNewServerDialog(activity, data) 48 | dialog.clearForm() 49 | closeDialog(activity) 50 | } 51 | onDismissCallBack?.let { dialog.dismissCallback = onDismissCallBack } 52 | } 53 | 54 | private fun initAnimations(activity: AppCompatActivity) { 55 | dialog.animationOriginView = activity.findViewById(R.id.button_new_server) 56 | } 57 | 58 | private fun onSetInitialUrl(url: String) { 59 | val padUrl = PadServer.Builder().padUrl(url) 60 | 61 | dialog.initialName = padUrl.host 62 | dialog.initialUrl = padUrl.server 63 | dialog.initialPrefix = padUrl.prefix 64 | } 65 | 66 | private fun saveNewServerDialog(activity: AppCompatActivity, data: Map) { 67 | activity.lifecycleScope.launch(Dispatchers.IO) { 68 | val server = Server() 69 | 70 | val updateServer = server.copy( 71 | mName = data["name"].toString(), 72 | mPadprefix = data["prefix"].toString(), 73 | mUrl = data["url"].toString(), 74 | mJquery = data["jquery"] as Boolean, 75 | mCryptPad = data["cryptpad"] as Boolean 76 | ) 77 | 78 | serverViewModel!!.insertServer(updateServer) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Activities/IntroActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Activities 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import com.github.appintro.AppIntro 7 | import com.github.appintro.AppIntroFragment 8 | import com.google.android.material.color.MaterialColors 9 | import com.mikifus.padland.Utils.Intro.LinkableAppIntroFragment 10 | import com.mikifus.padland.R 11 | 12 | /** 13 | * Created by mikifus on 7/10/16. 14 | */ 15 | class IntroActivity : AppIntro() { 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | setDoneTextAppearance(R.style.TextAppearance_AppCompat_Large) 20 | setSkipTextAppearance(R.style.TextAppearance_AppCompat_Large) 21 | setNextArrowColor(MaterialColors.getColor(this, R.attr.colorOnSurface, 0)) 22 | setBackArrowColor(MaterialColors.getColor(this, R.attr.colorOnSurface, 0)) 23 | setIndicatorColor( 24 | MaterialColors.getColor(this, R.attr.colorOnSurface, 0), 25 | getColor(R.color.material_on_surface_disabled) 26 | ) 27 | 28 | // Note here that we DO NOT use setContentView(); 29 | 30 | // Just set a title, description, background and image. AppIntro will do the rest. 31 | addSlide(AppIntroFragment.createInstance(getString(R.string.intro_first_title), 32 | getString(R.string.intro_first_desc), 33 | R.drawable.intro_image1)) 34 | addSlide(AppIntroFragment.createInstance(getString(R.string.intro_second_title), 35 | getString(R.string.intro_second_desc), 36 | R.drawable.intro_image2)) 37 | addSlide(AppIntroFragment.createInstance(getString(R.string.intro_third_title), 38 | getString(R.string.intro_third_desc), 39 | R.drawable.intro_image3)) 40 | addSlide(AppIntroFragment.createInstance(getString(R.string.intro_fourth_title), 41 | getString(R.string.intro_fourth_desc), 42 | R.drawable.intro_image4)) 43 | addSlide( 44 | LinkableAppIntroFragment.createInstance( 45 | getString(R.string.intro_fifth_title), 46 | getString(R.string.intro_fifth_desc), 47 | R.mipmap.ic_launcher)) 48 | 49 | // OPTIONAL METHODS 50 | // Override bar/separator color. 51 | // setBarColor(Color.parseColor("#3F51B5")); 52 | // setSeparatorColor(Color.parseColor("#2196F3")); 53 | // setTransformer 54 | 55 | // Hide Skip/Done button. 56 | isSkipButtonEnabled = true 57 | } 58 | 59 | override fun onSkipPressed(currentFragment: Fragment?) { 60 | super.onSkipPressed(currentFragment) 61 | 62 | // Launch real main activity 63 | launchMainActivity() 64 | } 65 | 66 | override fun onDonePressed(currentFragment: Fragment?) { 67 | super.onDonePressed(currentFragment) 68 | 69 | // Launch real main activity 70 | launchMainActivity() 71 | } 72 | 73 | private fun launchMainActivity() { 74 | val mainIntent = Intent(this@IntroActivity, PadListActivity::class.java) 75 | startActivity(mainIntent) 76 | // Finish current activity to avoid back button taking the user here 77 | finish() 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/pad_list_recyclerview_item_pad.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 25 | 26 | 36 | 37 | 44 | 45 | 51 | 52 | 53 | 63 | 64 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesEditPadGroupDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers; 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.FragmentTransaction 6 | import androidx.lifecycle.Observer 7 | import androidx.lifecycle.ViewModelProvider 8 | import androidx.lifecycle.lifecycleScope 9 | import com.mikifus.padland.Activities.PadListActivity 10 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 11 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 12 | import com.mikifus.padland.Dialogs.ConfirmDialog 13 | import com.mikifus.padland.Dialogs.EditPadGroupDialog 14 | import com.mikifus.padland.Dialogs.NewPadGroupDialog 15 | import kotlinx.coroutines.CoroutineScope 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.Dispatchers.Main 18 | import kotlinx.coroutines.launch 19 | 20 | interface IManagesEditPadGroupDialog { 21 | var padGroupViewModel: PadGroupViewModel? 22 | fun showEditPadGroupDialog(activity: AppCompatActivity, 23 | id: Long, 24 | animationOriginView: View? = null) 25 | } 26 | 27 | public class ManagesEditPadGroupDialog: ManagesDialog(), IManagesEditPadGroupDialog { 28 | override val DIALOG_TAG: String = "DIALOG_EDIT_PADGROUP" 29 | 30 | override val dialog by lazy { EditPadGroupDialog() } 31 | 32 | override var padGroupViewModel: PadGroupViewModel? = null 33 | 34 | override fun showEditPadGroupDialog(activity: AppCompatActivity, 35 | id: Long, 36 | animationOriginView: View?) { 37 | showDialog(activity) 38 | initViewModels(activity) 39 | initEvents(activity, id) 40 | initAnimations(animationOriginView) 41 | setData(activity, id) 42 | } 43 | 44 | private fun setData(activity: AppCompatActivity, id: Long) { 45 | activity.lifecycleScope.launch(Dispatchers.IO) { 46 | val padGroup = padGroupViewModel?.getById(id) 47 | 48 | val data = HashMap() 49 | padGroup?.mName?.let { 50 | data["name"] = it 51 | } 52 | 53 | activity.lifecycleScope.launch { 54 | dialog.setFormData(data) 55 | } 56 | } 57 | } 58 | 59 | private fun initViewModels(activity: AppCompatActivity) { 60 | if(padGroupViewModel == null) { 61 | padGroupViewModel = ViewModelProvider(activity)[PadGroupViewModel::class.java] 62 | } 63 | } 64 | 65 | private fun initEvents(activity: AppCompatActivity, id: Long) { 66 | dialog.setPositiveButtonCallback { data -> 67 | saveEditPadgroupDialog(activity, id, data["name"].toString()) 68 | dialog.clearForm() 69 | closeDialog(activity) 70 | } 71 | } 72 | 73 | private fun initAnimations(animationOriginView: View?) { 74 | animationOriginView.let { 75 | dialog.animationOriginView = animationOriginView 76 | } 77 | } 78 | 79 | private fun saveEditPadgroupDialog(activity: AppCompatActivity, id: Long, name: String) { 80 | activity.lifecycleScope.launch(Dispatchers.IO) { 81 | val padGroup = padGroupViewModel?.getById(id) 82 | 83 | padGroup?.mName = name 84 | 85 | if (padGroup != null) { 86 | padGroupViewModel!!.updatePadGroup(padGroup) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 15 | 16 | 20 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 34 | 35 | 42 | 43 | 50 | 51 | 52 | 53 | 55 | 56 | 61 | 62 | 67 | 68 | 72 | 75 | 76 | 77 | 81 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/Export/ImportHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils.Export 2 | 3 | import androidx.activity.result.ActivityResultLauncher 4 | import androidx.activity.result.contract.ActivityResultContracts 5 | import androidx.fragment.app.FragmentActivity 6 | import androidx.lifecycle.lifecycleScope 7 | import com.google.gson.GsonBuilder 8 | import com.google.gson.reflect.TypeToken 9 | import com.mikifus.padland.Database.PadGroupModel.PadGroupRepository 10 | import com.mikifus.padland.Database.PadListDatabase 11 | import com.mikifus.padland.Database.PadModel.PadRepository 12 | import com.mikifus.padland.Database.ServerModel.ServerRepository 13 | import com.mikifus.padland.Utils.Export.ExclusionStrategies.IgnoreEntityIdStrategy 14 | import com.mikifus.padland.Utils.Export.Maps.DatabaseMap 15 | import com.mikifus.padland.Utils.Export.TypeAdapters.SqlDateTypeAdapter 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.launch 18 | import kotlinx.coroutines.withContext 19 | import java.lang.reflect.Type 20 | import java.sql.Date 21 | 22 | interface IImportHelper { 23 | val activity: FragmentActivity 24 | val launcher: ActivityResultLauncher> 25 | 26 | } 27 | class ImportHelper( 28 | override val activity: FragmentActivity, 29 | callback: ((done: Boolean, result: String?) -> Unit) = {_,_->}) 30 | : IImportHelper { 31 | 32 | override val launcher = 33 | activity.registerForActivityResult( 34 | ActivityResultContracts.OpenDocument() 35 | ) { 36 | if (it == null) { 37 | callback(false, null) 38 | return@registerForActivityResult 39 | } 40 | val jsonString = activity.contentResolver 41 | .openInputStream(it) 42 | ?.bufferedReader() 43 | ?.readText() 44 | ?: "[]" 45 | 46 | val database = PadListDatabase.getInstance(activity) 47 | 48 | val serverRepository = ServerRepository(database.serverDao()) 49 | val padGroupRepository = PadGroupRepository(database.padGroupDao()) 50 | val padRepository = PadRepository(database.padDao()) 51 | 52 | val gson = GsonBuilder() 53 | .setVersion(database.openHelper.readableDatabase.version.toDouble()) 54 | .registerTypeAdapter(Date::class.java, SqlDateTypeAdapter) 55 | .setExclusionStrategies( 56 | IgnoreEntityIdStrategy() 57 | ) 58 | .setPrettyPrinting() 59 | .create() 60 | 61 | val listType: Type = object : TypeToken() {}.type 62 | val dataMap: DatabaseMap = gson.fromJson(jsonString, listType) 63 | 64 | activity.lifecycleScope.launch(Dispatchers.IO) { 65 | var size = 0L 66 | dataMap.padland_servers?.let { it -> 67 | size += serverRepository.insertServers(it).size 68 | } 69 | dataMap.padgroups?.let { it -> 70 | size += padGroupRepository.insertPadGroups(it).size 71 | } 72 | dataMap.padlist?.let { it -> 73 | size += padRepository.insertPads(it).size 74 | } 75 | dataMap.padlist_padgroups?.let { it -> 76 | size += padGroupRepository.insertPadGroupWithPadlistByRelString(it).size 77 | } 78 | 79 | val insertedResult = "$size" 80 | 81 | withContext(Dispatchers.Main) { 82 | callback(true, insertedResult) 83 | } 84 | } 85 | 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Adapters/ServerSelectionTracker/MakesServerSelectionTracker.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Adapters.ServerSelectionTracker 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.appcompat.view.ActionMode 6 | import androidx.recyclerview.selection.SelectionPredicates 7 | import androidx.recyclerview.selection.SelectionTracker 8 | import androidx.recyclerview.selection.StorageStrategy 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.mikifus.padland.ActionModes.ServerActionModeCallback 11 | import com.mikifus.padland.Activities.ServerListActivity 12 | import com.mikifus.padland.Adapters.ServerAdapter 13 | import com.mikifus.padland.R 14 | 15 | interface IMakesServerSelectionTracker { 16 | var serverSelectionTracker: SelectionTracker? 17 | var lastSelectedServerView: View? 18 | var serverActionMode: ActionMode? 19 | fun makeServerSelectionTracker(activity: ServerListActivity, recyclerView: RecyclerView, serverAdapter: ServerAdapter): SelectionTracker 20 | fun getServerSelection(): List 21 | fun onDestroyServerActionMode() 22 | } 23 | class MakesServerSelectionTracker: IMakesServerSelectionTracker { 24 | 25 | override var serverSelectionTracker: SelectionTracker? = null 26 | override var lastSelectedServerView: View? = null 27 | override var serverActionMode: ActionMode? = null 28 | var activity: AppCompatActivity? = null 29 | 30 | override fun makeServerSelectionTracker(activity: ServerListActivity, recyclerView: RecyclerView, serverAdapter: ServerAdapter): SelectionTracker { 31 | this.activity = activity 32 | serverSelectionTracker = SelectionTracker.Builder( 33 | "padSelectionTracker", 34 | recyclerView, 35 | ServerKeyProvider(serverAdapter), 36 | ServerDetailsLookup(recyclerView), 37 | StorageStrategy.createLongStorage() 38 | ) 39 | .withSelectionPredicate(SelectionPredicates.createSelectAnything()) 40 | .build() 41 | 42 | serverSelectionTracker!!.addObserver(object : SelectionTracker.SelectionObserver() { 43 | override fun onSelectionChanged() { 44 | super.onSelectionChanged() 45 | 46 | if (serverActionMode == null) { 47 | serverActionMode = 48 | activity.startSupportActionMode(ServerActionModeCallback(activity)) 49 | } 50 | 51 | val selectionCount = getServerSelection().size 52 | if (selectionCount > 0) { 53 | serverActionMode?.title = "$selectionCount " + activity.getString(R.string.model_server) 54 | 55 | recyclerView 56 | .findViewHolderForItemId( 57 | getServerSelection().last() 58 | )?.let { 59 | lastSelectedServerView = (it as ServerAdapter.ServerViewHolder).itemLayout 60 | } 61 | } else if(serverActionMode != null) { 62 | finishActionMode() 63 | } 64 | } 65 | }) 66 | 67 | return serverSelectionTracker!! 68 | } 69 | 70 | override fun getServerSelection(): List { 71 | return serverSelectionTracker!!.selection.toList() 72 | } 73 | 74 | override fun onDestroyServerActionMode() { 75 | if(serverSelectionTracker != null) { 76 | serverActionMode = null 77 | serverSelectionTracker!!.clearSelection() 78 | } 79 | } 80 | 81 | fun finishActionMode() { 82 | serverActionMode?.finish() 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/PadUrl.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils 2 | 3 | import android.net.Uri 4 | import android.util.Xml 5 | import java.net.MalformedURLException 6 | import java.net.URI 7 | import java.net.URL 8 | import java.net.URLEncoder 9 | 10 | /** 11 | * Created by mikifus on 16/03/17. 12 | */ 13 | class PadUrl private constructor(builder: Builder) { 14 | val padName: String? 15 | val padServer: String? 16 | private val padPrefix: String? 17 | 18 | init { 19 | padName = builder.name 20 | padServer = builder.server 21 | padPrefix = builder.prefix 22 | } 23 | 24 | @get:Throws(MalformedURLException::class) 25 | val url: URL 26 | get() = URL(string) 27 | val string: String 28 | get() = makeBaseUrl() + padName 29 | 30 | private fun makeBaseUrl(): String { 31 | var localPrefix = padPrefix 32 | if (localPrefix!!.isEmpty()) { 33 | throw RuntimeException("The pad url was not correctly built. Check the configuration for this server ($padServer).") 34 | } 35 | // Must end with / 36 | if (!localPrefix.endsWith("/")) { 37 | localPrefix = "$localPrefix/" 38 | } 39 | return localPrefix 40 | } 41 | 42 | override fun toString(): String { 43 | return string 44 | } 45 | 46 | class Builder { 47 | var name: String? = null 48 | var server: String? = null 49 | var prefix: String? = null 50 | fun padName(name: String): Builder { 51 | var name = name 52 | name = name.replace(" ".toRegex(), "_") 53 | this.name = name 54 | return this 55 | } 56 | 57 | fun padServer(server: String?): Builder { 58 | val parsedServer = server!!.replace("/$".toRegex(), "") // Remove trailing slash 59 | this.server = parsedServer 60 | return this 61 | } 62 | 63 | fun padPrefix(prefix: String?): Builder { 64 | val parsedPrefix = prefix!!.replace("/$".toRegex(), "") // Remove trailing slash 65 | this.prefix = parsedPrefix 66 | return this 67 | } 68 | 69 | fun build(): PadUrl { 70 | return PadUrl(this) 71 | } 72 | } 73 | 74 | companion object { 75 | 76 | fun etherpadAddUsernameAndColor(url: String, username: String?, color: Int?): String { 77 | val uri = URI(url) 78 | 79 | val queryParams = StringBuilder(uri.query.orEmpty()) 80 | 81 | if(username?.isBlank() == false) { 82 | queryParams 83 | .append('&') 84 | .append(URLEncoder.encode("userName", Xml.Encoding.UTF_8.name)) 85 | .append("=") 86 | .append(URLEncoder.encode(username, Xml.Encoding.UTF_8.name)) 87 | } 88 | if(color != null && color != 0) { 89 | val colorString = "#" + Integer.toHexString(color).substring(0, 6) 90 | queryParams 91 | .append('&') 92 | .append(URLEncoder.encode("userColor", Xml.Encoding.UTF_8.name)) 93 | .append("=") 94 | .append(colorString) 95 | } 96 | 97 | return Uri.Builder() 98 | .scheme(uri.scheme) 99 | .authority(uri.authority) 100 | .path(uri.path) 101 | .encodedQuery(queryParams.toString()) 102 | .encodedFragment(uri.fragment) 103 | .build() 104 | .toString() 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadModel/PadEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database.PadModel 2 | 3 | import android.content.ContentValues 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.room.ColumnInfo 7 | import androidx.room.Entity 8 | import androidx.room.PrimaryKey 9 | import com.mikifus.padland.Utils.PadServer 10 | import java.sql.Date 11 | 12 | @Entity(tableName = Pad.TABLE_NAME) 13 | data class Pad( 14 | @PrimaryKey(autoGenerate = true) 15 | @ColumnInfo(name = "_id") val mId: Long, 16 | @ColumnInfo(name = "name") val mName: String, 17 | @ColumnInfo(name = "local_name") val mLocalName: String, 18 | @ColumnInfo(name = "server") val mServer: String, 19 | @ColumnInfo(name = "url") val mUrl: String, 20 | @ColumnInfo(name = "last_used_date", defaultValue = "(strftime('%s','now'))") val mLastUsedDate: Date, 21 | @ColumnInfo(name = "create_date", defaultValue = "(strftime('%s','now'))") val mCreateDate: Date, 22 | @ColumnInfo(name = "access_count", defaultValue = "0") val mAccessCount: Long, 23 | // @ColumnInfo(name = "position") val mPosition: Int, 24 | ) 25 | { 26 | constructor() : this( 27 | 0, 28 | "", 29 | "", 30 | "", 31 | "", 32 | Date(System.currentTimeMillis()), 33 | Date(System.currentTimeMillis()), 34 | 0, 35 | // 0 36 | ) 37 | 38 | companion object { 39 | const val TABLE_NAME = "padlist" 40 | val PKEY = Pad::mId.name 41 | 42 | fun withOnlyId(id: Long): MutableLiveData { 43 | return MutableLiveData(Pad().copy(mId = id)) 44 | } 45 | 46 | fun fromContentValues(contentValues: ContentValues): MutableLiveData { 47 | val item = MutableLiveData(Pad()) 48 | 49 | // contentValues.valueSet().forEach { item.value = item.value?.copy(contentValues.getAsString(it.key)) } 50 | // item.value = item.value?.copy(mId=contentValues.getAsLong(Pad::mId::class.java.canonicalName)) 51 | if(contentValues.containsKey("name")) { 52 | item.value = item.value!!.copy(mName = contentValues.getAsString("name")) 53 | } 54 | if(contentValues.containsKey("url")) { 55 | item.value = item.value!!.copy(mUrl = contentValues.getAsString("url")) 56 | } 57 | 58 | return item 59 | } 60 | 61 | fun fromData(data: Map): MutableLiveData { 62 | val item = MutableLiveData(Pad()) 63 | data["name"]?.let { item.value = item.value!!.copy(mName = it as String) } 64 | data["local_name"]?.let { item.value = item.value!!.copy(mLocalName = it as String) } 65 | data["url"]?.let { item.value = item.value!!.copy(mUrl = it as String) } 66 | data["server"]?.let { item.value = item.value!!.copy(mServer = it as String) } 67 | 68 | return item 69 | } 70 | 71 | fun fromUrl(padUrl: String, context: AppCompatActivity? = null): MutableLiveData { 72 | val item = MutableLiveData(Pad()) 73 | 74 | val padServer = PadServer.Builder().padUrl(padUrl, context).build() 75 | 76 | val name = padServer.padName?: "" 77 | val server = padServer.server?: "" 78 | 79 | item.value = item.value!!.copy(mUrl = padUrl, mName = name, mServer = server) 80 | 81 | return item 82 | } 83 | } 84 | 85 | fun isPartiallyDifferentFrom(pad: Pad): Boolean { 86 | return ( 87 | mName != pad.mName || 88 | mUrl != pad.mUrl || 89 | mLocalName != pad.mLocalName 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesEditServerDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers; 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.ServerModel.ServerViewModel 8 | import com.mikifus.padland.Dialogs.EditServerDialog 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | 12 | interface IManagesEditServerDialog { 13 | var serverViewModel: ServerViewModel? 14 | fun showEditServerDialog(activity: AppCompatActivity, 15 | id: Long, 16 | animationOriginView: View? = null) 17 | } 18 | 19 | public class ManagesEditServerDialog: ManagesDialog(), IManagesEditServerDialog { 20 | override val DIALOG_TAG: String = "DIALOG_EDIT_SERVER" 21 | 22 | override val dialog by lazy { EditServerDialog() } 23 | 24 | override var serverViewModel: ServerViewModel? = null 25 | 26 | override fun showEditServerDialog(activity: AppCompatActivity, 27 | id: Long, 28 | animationOriginView: View?) { 29 | showDialog(activity) 30 | initViewModels(activity) 31 | initEvents(activity, id) 32 | initAnimations(animationOriginView) 33 | setData(activity, id) 34 | } 35 | 36 | private fun initAnimations(animationOriginView: View?) { 37 | animationOriginView.let { 38 | dialog.animationOriginView = animationOriginView 39 | } 40 | } 41 | 42 | private fun setData(activity: AppCompatActivity, id: Long) { 43 | activity.lifecycleScope.launch(Dispatchers.IO) { 44 | val server = serverViewModel?.getById(id) 45 | 46 | val data = HashMap() 47 | server?.mName?.let { 48 | data["name"] = it 49 | } 50 | server?.mUrl?.let { 51 | data["url"] = it 52 | } 53 | server?.mPadprefix?.let { 54 | data["prefix"] = it 55 | } 56 | server?.mJquery?.let { 57 | data["jquery"] = it 58 | } 59 | server?.mCryptPad?.let { 60 | data["cryptpad"] = it 61 | } 62 | 63 | activity.lifecycleScope.launch { 64 | dialog.setFormData(data) 65 | } 66 | } 67 | } 68 | 69 | private fun initViewModels(activity: AppCompatActivity) { 70 | if(serverViewModel == null) { 71 | serverViewModel = ViewModelProvider(activity)[ServerViewModel::class.java] 72 | } 73 | } 74 | 75 | private fun initEvents(activity: AppCompatActivity, id: Long) { 76 | dialog.setPositiveButtonCallback { data -> 77 | saveEditServerDialog(activity, id, data) 78 | dialog.clearForm() 79 | closeDialog(activity) 80 | } 81 | } 82 | 83 | private fun saveEditServerDialog(activity: AppCompatActivity, id: Long, data: Map) { 84 | activity.lifecycleScope.launch(Dispatchers.IO) { 85 | val server = serverViewModel?.getById(id) 86 | 87 | val updateServer = server?.copy( 88 | mName = data["name"].toString(), 89 | mPadprefix = data["prefix"].toString(), 90 | mUrl = data["url"].toString(), 91 | mJquery = data["jquery"] as Boolean, 92 | mCryptPad = data["cryptpad"] as Boolean 93 | ) 94 | 95 | if (updateServer != null) { 96 | serverViewModel!!.updateServer(updateServer) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesGroupPadDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers; 2 | 3 | import android.view.View 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.lifecycleScope 7 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 8 | import com.mikifus.padland.Database.PadGroupModel.PadGroupsAndPadList 9 | import com.mikifus.padland.Database.PadModel.PadViewModel 10 | import com.mikifus.padland.Dialogs.GroupPadDialog 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | 14 | interface IManagesGroupPadDialog { 15 | var padViewModel: PadViewModel? 16 | var padGroupViewModel: PadGroupViewModel? 17 | fun showGroupPadDialog(activity: AppCompatActivity, 18 | ids: List, 19 | animationOriginView: View? = null) 20 | } 21 | 22 | public class ManagesGroupPadDialog: ManagesDialog(), IManagesGroupPadDialog { 23 | override val DIALOG_TAG: String = "DIALOG_EDIT_PAD" 24 | 25 | override val dialog by lazy { GroupPadDialog() } 26 | override var padViewModel: PadViewModel? = null 27 | override var padGroupViewModel: PadGroupViewModel? = null 28 | 29 | override fun showGroupPadDialog(activity: AppCompatActivity, 30 | ids: List, 31 | animationOriginView: View?) { 32 | showDialog(activity) 33 | initViewModels(activity) 34 | initEvents(activity, ids) 35 | initAnimations(animationOriginView) 36 | setData(activity, ids) 37 | } 38 | 39 | private fun initAnimations(animationOriginView: View?) { 40 | animationOriginView.let { 41 | dialog.animationOriginView = animationOriginView 42 | } 43 | } 44 | 45 | private fun setData(activity: AppCompatActivity, ids: List) { 46 | activity.lifecycleScope.launch(Dispatchers.IO) { 47 | val padGroupsAndPadList = padGroupViewModel?.getPadGroupsAndPadListByPadIds(ids) 48 | 49 | var mainGroup: Long = 0 50 | padGroupsAndPadList?.forEach { 51 | mainGroup = it.mGroupId 52 | } 53 | 54 | val data = HashMap() 55 | data["group_id"] = mainGroup 56 | 57 | activity.lifecycleScope.launch { 58 | dialog.setFormData(data) 59 | } 60 | } 61 | } 62 | 63 | private fun initViewModels(activity: AppCompatActivity) { 64 | if(padViewModel == null) { 65 | padViewModel = ViewModelProvider(activity)[PadViewModel::class.java] 66 | } 67 | if(padGroupViewModel == null) { 68 | padGroupViewModel = ViewModelProvider(activity)[PadGroupViewModel::class.java] 69 | } 70 | } 71 | 72 | private fun initEvents(activity: AppCompatActivity, ids: List) { 73 | dialog.setPositiveButtonCallback { data -> 74 | saveGroupPadDialog(activity, ids, data) 75 | dialog.clearForm() 76 | closeDialog(activity) 77 | } 78 | } 79 | 80 | private fun saveGroupPadDialog(activity: AppCompatActivity, ids: List, data: Map) { 81 | val groupId = data["group_id"] as Long 82 | activity.lifecycleScope.launch(Dispatchers.IO) { 83 | ids.forEach { 84 | padGroupViewModel?.deletePadGroupsAndPadList(it) 85 | if(data["group_id"] as Long > 0) { 86 | padGroupViewModel?.insertPadGroupsAndPadList( 87 | PadGroupsAndPadList( 88 | mGroupId = groupId, 89 | mPadId = it, 90 | ) 91 | ) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 41 | 42 | 43 | 44 | 49 | 50 | 58 | 59 | 60 | 61 | 67 | 68 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/GroupPadDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ArrayAdapter 10 | import androidx.lifecycle.ViewModelProvider 11 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 12 | import com.mikifus.padland.Database.PadGroupModel.PadGroupViewModel 13 | import com.mikifus.padland.R 14 | import com.mikifus.padland.Utils.Views.Helpers.SpinnerHelper 15 | 16 | 17 | /** 18 | * Created by mikifus on 10/03/16. 19 | */ 20 | class GroupPadDialog: FormDialog() { 21 | 22 | private var data: Map? = null 23 | 24 | private var padGroupViewModel: PadGroupViewModel? = null 25 | 26 | private var mPadGroupSpinner: SpinnerHelper? = null 27 | 28 | private var padGroupsSpinnerData: List? = listOf() 29 | 30 | override fun setFormData(data: HashMap) { 31 | this.data = data 32 | applyFormData() 33 | } 34 | 35 | private fun applyFormData() { 36 | data?.get("group_id")?.let { 37 | val index = padGroupsSpinnerData?.indexOfFirst { it.mId == data!!["group_id"] }!! 38 | mPadGroupSpinner?.selectedItemPosition = if(index > -1) index else 0 39 | } 40 | } 41 | 42 | private fun initViewModels() { 43 | if(padGroupViewModel == null) { 44 | padGroupViewModel = ViewModelProvider(this)[PadGroupViewModel::class.java] 45 | } 46 | 47 | padGroupViewModel!!.getAll.observe(this) { padGroups -> 48 | padGroupsSpinnerData = listOf( 49 | PadGroup.fromName(getString(R.string.padlist_group_unclassified_name)).value!! 50 | ) + padGroups 51 | 52 | mPadGroupSpinner?.setAdapter( 53 | ArrayAdapter( 54 | requireContext(), 55 | R.layout.recyclerview_item, 56 | padGroupsSpinnerData!!.map { it.mName } 57 | ) 58 | ) 59 | 60 | mPadGroupSpinner?.selectedItemPosition = 0 61 | applyFormData() 62 | } 63 | } 64 | 65 | override fun validateForm(): Boolean { 66 | return true 67 | } 68 | 69 | override fun getFormData(): Map { 70 | val data = HashMap() 71 | data["group_id"] = padGroupsSpinnerData!![mPadGroupSpinner!!.selectedItemPosition].mId 72 | 73 | return data 74 | } 75 | 76 | override fun clearForm() { 77 | mPadGroupSpinner!!.setSelection(0) 78 | } 79 | 80 | override fun onStart() { 81 | super.onStart() 82 | mPadGroupSpinner = requireView().findViewById(R.id.spinner_pad_pad_group) 83 | } 84 | 85 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 86 | super.onCreateView(inflater, container, savedInstanceState) 87 | 88 | return inflater.inflate(R.layout.dialog_group_pad, container, false) 89 | } 90 | 91 | override fun initToolBar() { 92 | super.initToolBar() 93 | 94 | toolbar!!.title = getString(R.string.padlist_group_select_dialog) 95 | } 96 | 97 | /** 98 | * initViewModels() must be called here. The spinners will 99 | * only set the adapter when called from onResume(). 100 | * 101 | * 102 | * @see SpinnerHelper 103 | * @see https://github.com/material-components/material-components-android/issues/1464 104 | * @see https://github.com/material-components/material-components-android/issues/2012 105 | */ 106 | override fun onResume() { 107 | super.onResume() 108 | initViewModels() 109 | } 110 | 111 | override fun getTheme(): Int = R.style.DialogStyleMinWidth 112 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Utils/PadServer.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Utils 2 | 3 | import android.content.res.Resources 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.mikifus.padland.R 6 | import java.net.URL 7 | 8 | class PadServer private constructor(builder: PadServer.Builder) { 9 | val server: String? 10 | val host: String? 11 | val prefix: String? 12 | val padName: String? 13 | val baseUrl: String? 14 | 15 | override fun toString(): String { 16 | return baseUrl?: "" 17 | } 18 | 19 | init { 20 | server = builder.server 21 | host = builder.host 22 | prefix = builder.prefix 23 | padName = builder.padName 24 | baseUrl = builder.baseUrl 25 | } 26 | 27 | class Builder { 28 | var url: String? = null 29 | var server: String? = null 30 | var host: String? = null 31 | var prefix: String? = null 32 | var padName: String? = null 33 | var baseUrl: String? = null 34 | 35 | fun padUrl(url: String, context: AppCompatActivity? = null): Builder { 36 | val urlObject = URL(url) 37 | this.url = url 38 | this.host = urlObject.host.toString() 39 | 40 | this.padName = getNameFromCryptPadUrl(url, context?.resources) 41 | this.padName = this.padName.toString().ifBlank { urlObject.file.substring( 42 | urlObject.file.lastIndexOf("/") + 1 43 | ) } 44 | 45 | this.server = urlObject.protocol + "://" + urlObject.authority 46 | 47 | this.prefix = getPrefixFromUrl(url, context?.resources) 48 | if(this.prefix == "/") { 49 | this.prefix = "" 50 | } 51 | 52 | this.baseUrl = getBaseUrlFromServerAndPrefix(this.server.toString(), this.prefix.toString(), context?.resources) 53 | 54 | return this 55 | } 56 | 57 | private fun getPrefixFromUrl(url: String, resources: Resources? = null): String { 58 | val cutUrl = url.substring(this.server!!.length) 59 | 60 | if (resources !== null) { 61 | val cryptPadPrefixes = resources.getStringArray(R.array.prefixes_cryptpad) 62 | for (prefix in cryptPadPrefixes) { 63 | if (cutUrl.contains(prefix)) { 64 | return cutUrl.substring(cutUrl.indexOf(prefix), prefix.length) 65 | } 66 | } 67 | } 68 | 69 | return cutUrl.substring(0, cutUrl.lastIndexOf("/") + 1) 70 | } 71 | 72 | private fun getNameFromCryptPadUrl(url: String, resources: Resources?): String { 73 | if (resources !== null) { 74 | val cryptPadPrefixes = resources.getStringArray(R.array.prefixes_cryptpad) 75 | for(prefix in cryptPadPrefixes) { 76 | if(url.contains(prefix)) { 77 | return url.substring(url.lastIndexOf(prefix) + prefix.length) 78 | } 79 | } 80 | } 81 | return "" 82 | } 83 | 84 | private fun getBaseUrlFromServerAndPrefix(server: String, padPrefix: String, resources: Resources? = null): String { 85 | var baseUrl = server + padPrefix 86 | 87 | if (resources !== null) { 88 | val cryptPadPrefixes = resources.getStringArray(R.array.prefixes_cryptpad) 89 | for(prefix in cryptPadPrefixes) { 90 | if(padPrefix == prefix) { 91 | baseUrl = server 92 | break 93 | } 94 | } 95 | } 96 | 97 | if(!baseUrl.endsWith("/")) { 98 | baseUrl += "/" 99 | } 100 | 101 | return baseUrl 102 | } 103 | 104 | fun build(): PadServer { 105 | return PadServer(this) 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Activities/ServerListActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Activities 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.appcompat.widget.LinearLayoutCompat 7 | import androidx.core.view.ViewCompat 8 | import androidx.lifecycle.ViewModelProvider 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.google.android.material.floatingactionbutton.FloatingActionButton 12 | import com.mikifus.padland.Adapters.ServerAdapter 13 | import com.mikifus.padland.Adapters.ServerSelectionTracker.IMakesServerSelectionTracker 14 | import com.mikifus.padland.Adapters.ServerSelectionTracker.MakesServerSelectionTracker 15 | import com.mikifus.padland.Database.ServerModel.ServerViewModel 16 | import com.mikifus.padland.Dialogs.Managers.IManagesEditServerDialog 17 | import com.mikifus.padland.Dialogs.Managers.IManagesNewServerDialog 18 | import com.mikifus.padland.Dialogs.Managers.ManagesEditServerDialog 19 | import com.mikifus.padland.Dialogs.Managers.ManagesNewServerDialog 20 | import com.mikifus.padland.R 21 | 22 | class ServerListActivity: AppCompatActivity(), 23 | IMakesServerSelectionTracker by MakesServerSelectionTracker(), 24 | IManagesNewServerDialog by ManagesNewServerDialog(), 25 | IManagesEditServerDialog by ManagesEditServerDialog() { 26 | 27 | override var serverViewModel: ServerViewModel? = null 28 | private var serverAdapter: ServerAdapter? = null 29 | private var recyclerView: RecyclerView? = null 30 | private var mEmptyLayout: LinearLayoutCompat? = null 31 | private var mNewServerButton: FloatingActionButton? = null 32 | 33 | public override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | setContentView(R.layout.activity_server_list) 36 | setSupportActionBar(findViewById(R.id.activity_toolbar)) 37 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 38 | 39 | serverViewModel = ViewModelProvider(this)[ServerViewModel::class.java] 40 | serverAdapter = ServerAdapter(this, getOnItemClickListener()) 41 | 42 | mEmptyLayout = findViewById(R.id.empty) 43 | recyclerView = findViewById(R.id.recyclerview) 44 | recyclerView!!.layoutManager = LinearLayoutManager(this) 45 | recyclerView!!.adapter = serverAdapter 46 | ViewCompat.setNestedScrollingEnabled(recyclerView!!, false) 47 | 48 | mNewServerButton = findViewById(R.id.button_new_server) 49 | 50 | initListView() 51 | initEvents() 52 | } 53 | 54 | /* 55 | This method shall be used to initialize the list view using observer, 56 | here onChanged shall be triggered realtime as the data changes 57 | */ 58 | private fun initListView() { 59 | initSelectionTrackers() 60 | 61 | serverViewModel!!.getAll.observe(this) { serverList -> 62 | serverAdapter!!.data = serverList 63 | showHideEmpty(serverList.isEmpty()) 64 | } 65 | } 66 | 67 | private fun initSelectionTrackers() { 68 | serverAdapter!!.tracker = makeServerSelectionTracker(this, recyclerView!!, serverAdapter!!) 69 | } 70 | 71 | private fun initEvents() { 72 | mNewServerButton?.setOnClickListener { 73 | finishAllActionModes() 74 | showNewServerDialog(this) 75 | } 76 | } 77 | 78 | private fun getOnItemClickListener(): View.OnClickListener { 79 | return View.OnClickListener{ view: View -> 80 | showEditServerDialog(this, view.tag as Long, view) 81 | } 82 | } 83 | 84 | private fun showHideEmpty(visible: Boolean) { 85 | if(mEmptyLayout != null) { 86 | mEmptyLayout!!.visibility = if (visible) { 87 | View.VISIBLE 88 | } else { 89 | View.GONE 90 | } 91 | } 92 | } 93 | 94 | private fun finishAllActionModes() { 95 | serverActionMode?.finish() 96 | } 97 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Dialogs/Managers/ManagesWhitelistServerDialog.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Dialogs.Managers 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.text.TextPaint 7 | import android.text.TextUtils 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.fragment.app.FragmentTransaction 10 | import androidx.lifecycle.ViewModelProvider 11 | import com.mikifus.padland.Database.ServerModel.ServerViewModel 12 | import com.mikifus.padland.Dialogs.ConfirmDialog 13 | import com.mikifus.padland.R 14 | 15 | interface IManagesWhitelistServerDialog { 16 | var serverViewModel: ServerViewModel? 17 | fun showWhitelistServerDialog(activity: AppCompatActivity, 18 | url: String, 19 | onAddCallback: (dialogUrl: String) -> Unit, 20 | onNegativeCallback: (dialogUrl: String) -> Unit, 21 | onIgnoreCallback: (dialogUrl: String) -> Unit) 22 | } 23 | class ManagesWhitelistServerDialog: ManagesDialog(), IManagesWhitelistServerDialog { 24 | override val DIALOG_TAG: String = "DIALOG_WHITELIST_SERVER" 25 | 26 | override val dialog by lazy { ConfirmDialog() } 27 | override var serverViewModel: ServerViewModel? = null 28 | 29 | override fun showWhitelistServerDialog(activity: AppCompatActivity, 30 | url: String, 31 | onAddCallback: (dialogUrl: String) -> Unit, 32 | onNegativeCallback: (dialogUrl: String) -> Unit, 33 | onIgnoreCallback: (dialogUrl: String) -> Unit) { 34 | initViewModels(activity) 35 | initEvents(activity, url, onAddCallback, onNegativeCallback, onIgnoreCallback) 36 | 37 | dialog.setTitle(activity.getString(R.string.whitelist_server_dialog_title)) 38 | dialog.setMessage(activity.getString( 39 | R.string.padview_toast_blacklist_url, 40 | ellipsizeUrl(url, 80) 41 | )) 42 | dialog.positiveButtonText = activity.getString(R.string.serverlist_dialog_new_server_title) 43 | dialog.negativeButtonText = activity.getString(R.string.whitelist_server_dialog_open_browser) 44 | dialog.neutralButtonText = activity.getString(R.string.ignore) 45 | 46 | dialog.show(activity.supportFragmentManager, DIALOG_TAG) 47 | } 48 | 49 | @Suppress("SameParameterValue") 50 | private fun ellipsizeUrl(url: String, maxLength: Int): String { 51 | return if (url.length <= maxLength) { 52 | url 53 | } else { 54 | url.take( 55 | maxLength - (maxLength / 2) - 2 56 | ) + 57 | Typography.ellipsis + 58 | url.takeLast( 59 | maxLength - (maxLength / 2) - 1 60 | ) 61 | } 62 | } 63 | 64 | private fun initViewModels(activity: AppCompatActivity) { 65 | if(serverViewModel == null) { 66 | serverViewModel = ViewModelProvider(activity)[ServerViewModel::class.java] 67 | } 68 | } 69 | 70 | private fun initEvents(activity: AppCompatActivity, 71 | url: String, 72 | onAddCallback: (dialogUrl: String) -> Unit, 73 | onNegativeCallback: (dialogUrl: String) -> Unit, 74 | onIgnoreCallback: (dialogUrl: String) -> Unit) { 75 | dialog.neutralButtonCallback = DialogInterface.OnClickListener { dialog, which -> 76 | dialog.dismiss() 77 | onIgnoreCallback(url) 78 | } 79 | dialog.negativeButtonCallback = DialogInterface.OnClickListener { dialog, which -> 80 | dialog.dismiss() 81 | onNegativeCallback(url) 82 | } 83 | dialog.positiveButtonCallback = DialogInterface.OnClickListener { dialog, which -> 84 | onAddCallback(url) 85 | } 86 | dialog.isCancelable = false 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/pad_list_recyclerview_item_padgroup.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 28 | 29 | 46 | 47 | 61 | 62 | 63 | 64 | 72 | 73 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/mikifus/padland/Database/PadListDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.mikifus.padland.Database 2 | 3 | import android.content.Context 4 | import android.database.sqlite.SQLiteDatabase 5 | import android.database.sqlite.SQLiteOpenHelper 6 | import android.util.Log 7 | import androidx.room.AutoMigration 8 | import androidx.room.Database 9 | import androidx.room.Room 10 | import androidx.room.RoomDatabase 11 | import androidx.room.TypeConverters 12 | import com.mikifus.padland.Database.Migrations.CompatibilityMigration 13 | import com.mikifus.padland.Database.Migrations.MIGRATION_BEFORE_ROOM 14 | import com.mikifus.padland.Database.PadGroupModel.PadGroup 15 | import com.mikifus.padland.Database.PadGroupModel.PadGroupDao 16 | import com.mikifus.padland.Database.PadGroupModel.PadGroupsAndPadList 17 | import com.mikifus.padland.Database.PadModel.Pad 18 | import com.mikifus.padland.Database.PadModel.PadDao 19 | import com.mikifus.padland.Database.ServerModel.Server 20 | import com.mikifus.padland.Database.ServerModel.ServerDao 21 | import com.mikifus.padland.Database.TypeConverters.DateConverter 22 | 23 | 24 | @Database( 25 | entities = [ 26 | Pad::class, 27 | PadGroup::class, 28 | Server::class, 29 | PadGroupsAndPadList::class 30 | ], 31 | version = 9, 32 | autoMigrations = [ 33 | AutoMigration(from=8, to=9) 34 | ] 35 | ) 36 | @TypeConverters(DateConverter::class) 37 | abstract class PadListDatabase : RoomDatabase() { 38 | 39 | abstract fun padDao(): PadDao 40 | 41 | abstract fun padGroupDao(): PadGroupDao 42 | 43 | abstract fun serverDao(): ServerDao 44 | 45 | companion object { 46 | @Volatile 47 | private var INSTANCE: PadListDatabase? = null 48 | 49 | fun getInstance(context: Context): PadListDatabase { 50 | return INSTANCE?: synchronized(this){ 51 | val instance = buildDatabase(context) 52 | INSTANCE = instance 53 | instance 54 | } 55 | } 56 | 57 | private fun buildDatabase(context: Context): PadListDatabase { 58 | return getDatabaseBuilder(context).build() 59 | } 60 | 61 | private fun getDatabaseBuilder(context: Context): Builder { 62 | return Room.databaseBuilder(context, PadListDatabase::class.java, "padlist") 63 | .addMigrations(MIGRATION_BEFORE_ROOM/*, MIGRATION_8_9*/) 64 | } 65 | 66 | /** 67 | * WARNING: Use only for tests 68 | */ 69 | fun getMainThreadInstance(context: Context): PadListDatabase { 70 | return INSTANCE?: synchronized(this){ 71 | val instance = getDatabaseBuilder(context) 72 | .allowMainThreadQueries() 73 | .build() 74 | INSTANCE = instance 75 | instance 76 | } 77 | } 78 | 79 | /** 80 | * WARNING: Compatibility transformation 81 | */ 82 | fun migrateBeforeRoom(context: Context) { 83 | Log.w("MIGRATION_BEFORE_ROOM", "migrateBeforeRoom called") 84 | try { 85 | Log.w("MIGRATION_BEFORE_ROOM", "migrateBeforeRoom getting instance") 86 | getMainThreadInstance(context).inTransaction() 87 | Log.w("MIGRATION_BEFORE_ROOM", "migrateBeforeRoom got instance") 88 | } catch (exception: IllegalStateException) { 89 | Log.w("MIGRATION_BEFORE_ROOM", "migrateBeforeRoom got exception") 90 | Log.w("MIGRATION_BEFORE_ROOM", exception.stackTraceToString()) 91 | val db = PadlandDbHelper(context) 92 | CompatibilityMigration.realMigrate(context, db.writableDatabase) 93 | db.close() 94 | } 95 | } 96 | } 97 | 98 | 99 | 100 | open class PadlandDbHelper(protected open var context: Context?) : 101 | SQLiteOpenHelper(context, 102 | "padlist", 103 | null, 104 | 8) { 105 | override fun onCreate(p0: SQLiteDatabase?) {} 106 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {} 107 | } 108 | 109 | } 110 | 111 | -------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Padland 4 | 設定 5 | 設定 6 | 設定 7 | 作成または移動 8 | Padland ビューアー 9 | パッド名 10 | ドキュメントを追加 11 | パッドリストに戻る 12 | パッドを作成 13 | パッドの名前を導入します。 存在しない場合は作成します。 それ以外の場合はパッドを表示します。 14 | パッドを作成 15 | パッドを保存 16 | アプリについて 17 | アプリについて 18 | 19 | Licensed under the Apache ライセンス. 20 | 21 | 新しいドキュメントを自動保存 22 | 新しく訪問したドキュメントを自動的にリストに追加します 23 | 共有 24 | 削除 25 | 保存したドキュメントはありません。 26 | 選択したドキュメントを削除してもよろしいですか? 27 | 選択したグループを削除してもよろしいですか? 28 | OK 29 | キャンセル 30 | ドキュメントを共有 31 | "Padland に参加しよう! " 32 | ユーザー設定 33 | デフォルトのユーザー名 34 | PadLand は、開いたすべてのドキュメントにデフォルトのユーザー名を設定しようとします。 35 | デフォルトの色 36 | PadLand は、開いたすべてのドキュメントにデフォルトの色を設定しようとします。 37 | 色の選択 38 | 書式の例 (hex): #FF0000 39 | テキストをクリップボードにコピーしました。クリップボードのデータを貼り付けて使用してください。 40 | ネットワークが到達できません。 41 | Padland ドキュメント 42 | サーバーの選択 (任意): 43 | デフォルトのパッド サーバー 44 | 新しいサーバーを開くときの、デフォルトのパッド サーバーを選択してください。 45 | 46 | このアプリは開発中です。 フィードバックやバグ報告は、開発者 までご連絡ください。\n 47 | \n 48 | 貢献していただける場合は、こちらのリポジトリまで:\n 49 | https://github.com/mikifus/padland 50 | 51 | パッドを表示 52 | ドキュメント情報 53 | PadLandDataActivity 54 | 55 | ドキュメント情報 56 | ドキュメント URL 57 | リストに追加しました 58 | 最後に表示 59 | 60 | ドキュメントの名前を指定する必要があります。 61 | 62 | 新しいドキュメントを作成 63 | ドキュメントを削除しました 64 | グループを削除しました 65 | 新しいグループ 66 | 新しいグループの名前を入力してください 67 | グループ名が正しくありません。 68 | 未分類 69 | グループの選択 70 | 71 | 予期しないエラーが発生しました。 72 | その他 73 | アクセス回数 74 | 匿名 75 | 76 | --------------------------------------------------------------------------------
Licensed under the Apache ライセンス.
このアプリは開発中です。 フィードバックやバグ報告は、開発者 までご連絡ください。
貢献していただける場合は、こちらのリポジトリまで:\n 49 | https://github.com/mikifus/padland