├── .github ├── FUNDING.yml └── workflows │ └── gradle.yml ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── discord.xml ├── gradle.xml ├── jarRepositories.xml └── misc.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── openblocks │ │ └── android │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── openblocks │ │ │ └── android │ │ │ ├── AboutActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── ModuleConfigActivity.java │ │ │ ├── ProjectEditorActivity.java │ │ │ ├── README.md │ │ │ ├── SettingsActivity.java │ │ │ ├── adapters │ │ │ ├── ConfigRecyclerViewAdapter.java │ │ │ ├── ModulesRecyclerViewAdapter.java │ │ │ └── ProjectRecyclerViewAdapter.java │ │ │ ├── constants │ │ │ └── IncludedBinaries.java │ │ │ ├── dialog │ │ │ ├── NewProjectDialog.java │ │ │ └── ProjectMetadataEditDialog.java │ │ │ ├── fragments │ │ │ ├── main │ │ │ │ ├── ModulesFragment.java │ │ │ │ └── ProjectsFragment.java │ │ │ └── projecteditor │ │ │ │ ├── CodeEditFragment.java │ │ │ │ ├── LayoutEditFragment.java │ │ │ │ └── LogFragment.java │ │ │ ├── helpers │ │ │ ├── BlockCollectionParser.java │ │ │ └── FileHelper.java │ │ │ └── modman │ │ │ ├── ModuleJsonCorruptedException.java │ │ │ ├── ModuleLoader.java │ │ │ ├── ModuleLogger.java │ │ │ ├── ModuleManager.java │ │ │ └── models │ │ │ └── Module.java │ └── res │ │ ├── drawable-anydpi-v24 │ │ ├── ic_about.xml │ │ ├── ic_add_white.xml │ │ ├── ic_discord.xml │ │ ├── ic_home.xml │ │ ├── ic_settings.xml │ │ └── ic_website.xml │ │ ├── drawable-hdpi │ │ ├── ic_about.png │ │ ├── ic_add_white.png │ │ ├── ic_discord.png │ │ ├── ic_github.png │ │ ├── ic_home.png │ │ ├── ic_settings.png │ │ └── ic_website.png │ │ ├── drawable-mdpi │ │ ├── ic_about.png │ │ ├── ic_add_white.png │ │ ├── ic_discord.png │ │ ├── ic_github.png │ │ ├── ic_home.png │ │ ├── ic_settings.png │ │ └── ic_website.png │ │ ├── drawable-xhdpi │ │ ├── ic_about.png │ │ ├── ic_add_white.png │ │ ├── ic_discord.png │ │ ├── ic_github.png │ │ ├── ic_home.png │ │ ├── ic_settings.png │ │ └── ic_website.png │ │ ├── drawable-xxhdpi │ │ ├── ic_about.png │ │ ├── ic_add_white.png │ │ ├── ic_discord.png │ │ ├── ic_github.png │ │ ├── ic_home.png │ │ ├── ic_settings.png │ │ └── ic_website.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_discord.png │ │ └── ic_github.png │ │ ├── drawable │ │ ├── bg_module_item.xml │ │ ├── bg_module_item_ripple.xml │ │ ├── ic_about.xml │ │ ├── ic_add.xml │ │ ├── ic_code.xml │ │ ├── ic_compiler.xml │ │ ├── ic_dashboard_black_24dp.xml │ │ ├── ic_home_black_24dp.xml │ │ ├── ic_import.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_layout.xml │ │ ├── ic_notifications_black_24dp.xml │ │ ├── ic_project_manager.xml │ │ ├── ic_project_parser.xml │ │ ├── ic_run.xml │ │ └── ic_settings.xml │ │ ├── layout │ │ ├── _main_drawer_header.xml │ │ ├── activity_about.xml │ │ ├── activity_main.xml │ │ ├── activity_module_config.xml │ │ ├── activity_project_editor.xml │ │ ├── dialog_edit_project_metadata.xml │ │ ├── fragment_code_edit.xml │ │ ├── fragment_layout_edit.xml │ │ ├── fragment_log.xml │ │ ├── fragment_modules.xml │ │ ├── fragment_projects.xml │ │ ├── nav_header_main.xml │ │ ├── rv_config_item.xml │ │ ├── rv_module_item.xml │ │ ├── rv_project_item.xml │ │ └── settings_activity.xml │ │ ├── menu │ │ ├── _main_drawer_menu.xml │ │ └── activity_main_drawer.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ ├── colors.xml │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── root_preferences.xml │ └── test │ └── java │ └── com │ └── openblocks │ └── android │ └── ExampleUnitTest.java ├── build.gradle ├── copy_binaries.bat ├── copy_binaries.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: OpenBlocks 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main, dev ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Update and init submodules 20 | run: git submodule update --init 21 | - name: Set up JDK 1.8 22 | uses: actions/setup-java@v1 23 | with: 24 | java-version: 1.8 25 | - name: Grant execute permission for gradlew 26 | run: chmod +x gradlew 27 | - name: Build with Gradle 28 | run: ./gradlew build 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | .idea/vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | # Ignore the binaries 88 | app/src/main/jniLibs/**/ 89 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "openblocks-module-interface"] 2 | path = openblocks-module-interface 3 | url = https://github.com/OpenBlocksTeam/openblocks-module-interface 4 | [submodule "android-build-tools"] 5 | path = android-build-tools 6 | url = https://github.com/OpenBlocksTeam/android-build-tools 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | OpenBlocks -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 OpenBlocks 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

OpenBlocks

6 | 7 |

8 | 9 | Java CI with Gradle 10 | 11 | 12 | Discord server 13 | 14 | 15 | Donate to OpenBlocks 16 | 17 | Maintenance 18 |

19 | 20 | An open source, modular alternative of sketchware. Create your own app in android using block programming like scratch! 21 | 22 | ## What is OpenBlocks? 23 | OpenBlocks is a community-driven modular android application that can create android apps using block programming like scratch. Every component for OpenBlocks is modular, these modules can be loaded and unloaded at runtime, giving users freedom on what they want to do with this app. Create customizable blocks, Custom widgets, Custom manager, Custom project type, Custom everything, Anything you want with it! 24 | 25 | #### For Sketchware users... 26 | OpenBlocks is basically an app that you can mod easily! It's open source, change what you want, contribute what you want, use what you want! It's also modular, meaning you can create modules to do something else, For example, I want to add a TextInputLayout to my Layout editor, just download an already-existing module that adds TextInputLayout to the layout editor, and boom! That's it, No need to mod sketchware. And if you want to create a module, we have already made an interface for that, check out [openblocks-module-interface](https://github.com/OpenBlocksTeam/openblocks-module-interface), the guide is also there, check out it's [wiki page](https://github.com/OpenBlocksTeam/openblocks-module-communicator/wiki/Initial-Idea). 27 | 28 | Happy Coding! 29 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'androidx.navigation.safeargs' 4 | } 5 | 6 | android { 7 | 8 | splits { 9 | abi { 10 | enable true 11 | reset() 12 | include 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86' 13 | universalApk true 14 | } 15 | } 16 | 17 | compileSdkVersion 30 18 | buildToolsVersion "30.0.3" 19 | 20 | defaultConfig { 21 | applicationId "com.openblocks.android" 22 | minSdkVersion 21 23 | targetSdkVersion 30 24 | versionCode 1 25 | versionName "0.1-alpha" 26 | 27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 28 | } 29 | 30 | buildFeatures { 31 | viewBinding true 32 | } 33 | 34 | buildTypes { 35 | release { 36 | minifyEnabled false 37 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 38 | } 39 | } 40 | 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation project(":module-interface") 50 | 51 | implementation 'androidx.appcompat:appcompat:1.2.0' 52 | implementation 'com.google.android.material:material:1.3.0' 53 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 54 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 55 | implementation 'androidx.navigation:navigation-fragment:2.3.4' 56 | implementation 'androidx.navigation:navigation-ui:2.3.4' 57 | implementation 'androidx.preference:preference:1.1.1' 58 | 59 | testImplementation 'junit:junit:4.13.2' 60 | 61 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 63 | } 64 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/openblocks/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.openblocks.android", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.appcompat.widget.Toolbar; 5 | 6 | import android.os.Bundle; 7 | 8 | import com.openblocks.android.databinding.ActivityAboutBinding; 9 | 10 | public class AboutActivity extends AppCompatActivity { 11 | 12 | private ActivityAboutBinding _binding; 13 | private Toolbar _toolBar; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | _binding = ActivityAboutBinding.inflate(getLayoutInflater()); 19 | setContentView(_binding.getRoot()); 20 | 21 | _toolBar = _binding.toolbarAbout; 22 | setSupportActionBar(_toolBar); 23 | 24 | if (getSupportActionBar() != null) { 25 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 26 | getSupportActionBar().setHomeButtonEnabled(true); 27 | _toolBar.setNavigationOnClickListener(v -> onBackPressed()); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.view.MenuItem; 11 | import android.view.View; 12 | import android.widget.Toast; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.appcompat.app.ActionBarDrawerToggle; 17 | import androidx.appcompat.app.AppCompatActivity; 18 | import androidx.appcompat.widget.Toolbar; 19 | import androidx.core.view.GravityCompat; 20 | import androidx.drawerlayout.widget.DrawerLayout; 21 | import androidx.fragment.app.Fragment; 22 | import androidx.fragment.app.FragmentManager; 23 | import androidx.fragment.app.FragmentStatePagerAdapter; 24 | import androidx.viewpager.widget.ViewPager; 25 | 26 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 27 | import com.google.android.material.navigation.NavigationView; 28 | import com.google.android.material.tabs.TabLayout; 29 | import com.openblocks.android.databinding.ActivityMainBinding; 30 | import com.openblocks.android.fragments.main.ModulesFragment; 31 | import com.openblocks.android.fragments.main.ProjectsFragment; 32 | import com.openblocks.android.helpers.FileHelper; 33 | import com.openblocks.android.modman.ModuleJsonCorruptedException; 34 | import com.openblocks.android.modman.ModuleLoader; 35 | import com.openblocks.android.modman.ModuleLogger; 36 | import com.openblocks.android.modman.ModuleManager; 37 | import com.openblocks.android.modman.models.Module; 38 | import com.openblocks.android.dialog.NewProjectDialog; 39 | import com.openblocks.moduleinterface.OpenBlocksModule; 40 | import com.openblocks.moduleinterface.exceptions.ParseException; 41 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 42 | import com.openblocks.moduleinterface.models.OpenBlocksRawProject; 43 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksCode; 44 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksLayout; 45 | 46 | import org.json.JSONException; 47 | 48 | import java.io.File; 49 | import java.io.FileInputStream; 50 | import java.io.IOException; 51 | import java.util.ArrayList; 52 | import java.util.HashMap; 53 | 54 | public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { 55 | 56 | private static final String TAG = "MainActivity"; 57 | 58 | final int EDIT_METADATA_REQUEST_CODE = 2; 59 | final int IMPORT_MODULE_REQUEST_CODE = 1; 60 | 61 | // Used to list, get and read project 62 | OpenBlocksModule.ProjectManager project_manager; 63 | OpenBlocksModule.ProjectParser project_parser; 64 | 65 | // Used to initialize / create a new project 66 | OpenBlocksModule.BlocksCollection blocks_collection; 67 | OpenBlocksModule.ProjectLayoutGUI layout_editor; 68 | 69 | // List of existing project ids 70 | ArrayList project_ids; 71 | 72 | private ActivityMainBinding binding; 73 | 74 | private DrawerLayout _drawer; 75 | 76 | private FloatingActionButton fabProjects; 77 | private FloatingActionButton fabModules; 78 | 79 | private HashMap> modules; 80 | 81 | private ModuleLogger logger; 82 | 83 | @Override 84 | protected void onCreate(Bundle savedInstanceState) { 85 | super.onCreate(savedInstanceState); 86 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 87 | setContentView(binding.getRoot()); 88 | 89 | // Main Part (ActionBar) 90 | Toolbar _actionBar = binding.toolBar; 91 | setSupportActionBar(_actionBar); 92 | 93 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 94 | getSupportActionBar().setHomeButtonEnabled(true); 95 | 96 | // Drawer Toggle & Drawer 97 | _drawer = binding.drawerLayout; 98 | NavigationView _drawer_navView = binding.navView; 99 | 100 | ActionBarDrawerToggle _toggle = new ActionBarDrawerToggle(MainActivity.this, _drawer, _actionBar, R.string.app_name, R.string.app_name); 101 | _drawer.addDrawerListener(_toggle); 102 | _toggle.syncState(); 103 | 104 | _drawer_navView.setNavigationItemSelectedListener(this); 105 | 106 | // Load Modules ============================================================================ 107 | 108 | // Get the SharedPreferences 109 | SharedPreferences sp = getSharedPreferences("data", MODE_PRIVATE); 110 | 111 | // Check if this is the first time the user has opened this app 112 | if (sp.getBoolean("first_time", true)) { 113 | 114 | // Oo, first time huh, let's initialize the modules folder, and extract our default modules there 115 | try { 116 | // Initialize the modules folder 117 | File modules_folder = new File(getFilesDir(), "/modules/"); 118 | 119 | if (!modules_folder.mkdir()) { 120 | Log.w(TAG, "onCreate: modules folder already exists on init, continuing anyway"); 121 | } 122 | 123 | // Initialize the modules.json file 124 | File modulesjson = new File(modules_folder, "modules.json"); 125 | 126 | if (!modulesjson.createNewFile()) { 127 | Log.w(TAG, "onCreate: modules.json already exists on first time, continuing anyway"); 128 | } 129 | 130 | FileHelper.writeFile(modulesjson, "{\"modules\":{}, \"active_modules\":{}}".getBytes()); 131 | 132 | // TODO: EXTRACT / DOWNLOAD DEFAULT MODULES 133 | 134 | sp.edit().putBoolean("first_time", false).apply(); 135 | } catch (IOException e) { 136 | Toast.makeText(this, "Error while initializing modules: " + e.getMessage(), Toast.LENGTH_LONG).show(); 137 | e.printStackTrace(); 138 | finish(); 139 | } 140 | } 141 | 142 | // TODO: SHOW A LOADING BAR / SCREEN WHEN WE'RE LOADING MODULES 143 | ModuleManager moduleManager = ModuleManager.getInstance(); 144 | 145 | // Load modules 146 | try { 147 | moduleManager.fetchAllModules(this); 148 | 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | 152 | Toast.makeText(this, "Error while reading modules: " + e.getMessage(), Toast.LENGTH_LONG).show(); 153 | } catch (ModuleJsonCorruptedException e) { 154 | e.printStackTrace(); 155 | 156 | Toast.makeText(this, "modules.json is corrupted: " + e.getMessage(), Toast.LENGTH_LONG).show(); 157 | } 158 | 159 | modules = moduleManager.getModules(); 160 | 161 | // Load Modules ============================================================================ 162 | 163 | // Load Projects =========================================================================== 164 | 165 | logger = ModuleLogger.getInstance(); 166 | 167 | Module project_manager_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_MANAGER); 168 | Module project_parser_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_PARSER); 169 | Module blocks_collection_module = moduleManager.getActiveModule(OpenBlocksModule.Type.BLOCKS_COLLECTION); 170 | Module layout_editor_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_LAYOUT_GUI); 171 | 172 | project_manager = ModuleLoader.load(this, project_manager_module, OpenBlocksModule.ProjectManager.class); 173 | project_parser = ModuleLoader.load(this, project_parser_module, OpenBlocksModule.ProjectParser.class); 174 | blocks_collection = ModuleLoader.load(this, blocks_collection_module, OpenBlocksModule.BlocksCollection.class); 175 | layout_editor = ModuleLoader.load(this, layout_editor_module, OpenBlocksModule.ProjectLayoutGUI.class); 176 | 177 | if (project_manager != null) project_manager.initialize(this, logger); 178 | if (project_parser != null) project_parser.initialize(this, logger); 179 | if (layout_editor != null) layout_editor.initialize(this, logger); 180 | 181 | ArrayList projects_metadata = new ArrayList<>(); 182 | 183 | if (project_manager != null && project_parser != null) { 184 | // Only run these if the modules are successfully loaded 185 | ArrayList projects = project_manager.listProjects(); 186 | 187 | for (OpenBlocksRawProject project : projects) { 188 | project_ids.add(project.ID); 189 | 190 | try { 191 | projects_metadata.add(project_parser.parseMetadata(project)); 192 | } catch (ParseException e) { 193 | e.printStackTrace(); 194 | logger.err(project_parser.getClass(), ""); 195 | } 196 | } 197 | } 198 | 199 | // Load Projects =========================================================================== 200 | 201 | // View Pager 202 | ViewPager viewPager = binding.viewPager; 203 | TabLayout tabLayout = binding.tabLayout; 204 | 205 | viewPager.setAdapter(new FragmentAdapter(getApplicationContext(), getSupportFragmentManager(), 2, modules, projects_metadata)); 206 | tabLayout.setupWithViewPager(viewPager); 207 | 208 | // FABs 209 | fabProjects = binding.fabProjects; 210 | fabModules = binding.fabModules; 211 | 212 | fabModules.hide(); 213 | 214 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 215 | fabProjects.setTooltipText("New project"); 216 | fabModules.setTooltipText("Add module"); 217 | } 218 | 219 | // Listeners 220 | viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 221 | @Override 222 | public void onPageScrolled(int _position, float _positionOffset, int _positionOffsetPixels) { 223 | 224 | } 225 | 226 | @Override 227 | public void onPageSelected(int _position) { 228 | if (_position == 0) { 229 | fabProjects.show(); 230 | fabModules.hide(); 231 | } else { 232 | fabProjects.hide(); 233 | fabModules.show(); 234 | } 235 | } 236 | 237 | @Override 238 | public void onPageScrollStateChanged(int _scrollState) { 239 | 240 | } 241 | }); 242 | } 243 | 244 | // When the user clicked the "New Project" button 245 | public void fabProjectsClicked(View view) { 246 | // Handle a not implemented Project parser 247 | if (project_parser == null) { 248 | Toast.makeText(this, "No Project parser module has been loaded yet.", Toast.LENGTH_SHORT).show(); 249 | return; 250 | } 251 | // Show the "New project" dialog 252 | NewProjectDialog dialog = new NewProjectDialog(this, project_parser.generateFreeId(project_ids)) 253 | .addOnMetadataSavedListener((appName, packageName, versionName, versionCode) -> { 254 | // User has clicked the "OK" button (and the data is valid), Create a new project 255 | OpenBlocksProjectMetadata metadata = 256 | new OpenBlocksProjectMetadata( 257 | appName, 258 | packageName, 259 | versionName, 260 | versionCode 261 | ); 262 | 263 | String new_id = project_parser.generateFreeId(project_ids); 264 | 265 | // Initialize the project 266 | OpenBlocksCode initialized_code = blocks_collection.initializeNewCode(); 267 | 268 | // Set the block collection name 269 | initialized_code.block_collection_name = 270 | ModuleManager 271 | .getInstance() 272 | .getActiveModule(OpenBlocksModule.Type.BLOCKS_COLLECTION).name; 273 | 274 | OpenBlocksLayout initialized_layout = layout_editor.initializeNewLayout(); 275 | OpenBlocksRawProject new_project = project_parser.saveProject(metadata, initialized_code, initialized_layout); 276 | 277 | new_project.ID = new_id; 278 | 279 | // Save the project 280 | project_manager.saveProject(new_project); 281 | }); 282 | 283 | dialog.show(); 284 | } 285 | 286 | // When user clicked the "import" button 287 | public void fabModulesClicked(View view) { 288 | // Use SAF to pick a zip file, we ain't messing around with scoped storage 289 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT) 290 | .addCategory(Intent.CATEGORY_OPENABLE) 291 | .setType("application/zip"); 292 | 293 | startActivityForResult(intent, IMPORT_MODULE_REQUEST_CODE); 294 | } 295 | 296 | @Override 297 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 298 | super.onActivityResult(requestCode, resultCode, data); 299 | 300 | if (resultCode == RESULT_CANCELED || data == null) 301 | return; 302 | 303 | if (requestCode == IMPORT_MODULE_REQUEST_CODE) { 304 | // Get the URI 305 | Uri uri = data.getData(); 306 | ArrayList module; 307 | 308 | // Then import the module 309 | try { 310 | module = ModuleManager.getInstance().importModule(this, 311 | new FileInputStream(getContentResolver().openFileDescriptor(uri, "r").getFileDescriptor())); 312 | } catch (IOException e) { 313 | Toast.makeText(this, "Error while reading module: " + e.getMessage(), Toast.LENGTH_LONG).show(); 314 | 315 | return; 316 | } catch (JSONException e) { 317 | Toast.makeText(this, "Module is corrupted: " + e.getMessage(), Toast.LENGTH_LONG).show(); 318 | 319 | return; 320 | } 321 | 322 | StringBuilder module_string = new StringBuilder(); 323 | 324 | for (Module module1 : module) { 325 | module_string.append(module1.name).append(" "); 326 | } 327 | 328 | Toast.makeText(this, "Module " + module_string + "has successfully imported, restarting activity", Toast.LENGTH_SHORT).show(); 329 | 330 | // Ok then refresh our activity 331 | recreate(); 332 | } else if (requestCode == EDIT_METADATA_REQUEST_CODE) { 333 | OpenBlocksProjectMetadata metadata = 334 | new OpenBlocksProjectMetadata( 335 | data.getStringExtra("app_name"), 336 | data.getStringExtra("package_name"), 337 | data.getStringExtra("version_name"), 338 | Integer.parseInt(data.getStringExtra("version_code")) 339 | ); 340 | 341 | // Create a new project 342 | // TODO: Handle project_parser being null (for some reason) 343 | String new_id = project_parser.generateFreeId(project_ids); 344 | 345 | // Initialize the project 346 | OpenBlocksCode initialized_code = blocks_collection.initializeNewCode(); 347 | OpenBlocksLayout initialized_layout = layout_editor.initializeNewLayout(); 348 | OpenBlocksRawProject new_project = project_parser.saveProject(metadata, initialized_code, initialized_layout); 349 | 350 | // Save the project 351 | project_manager.saveProject(new_project); 352 | 353 | project_ids.add(new_id); 354 | 355 | // Then finally, open the project 356 | Intent i = new Intent(this, ProjectEditorActivity.class); 357 | i.putExtra("project_id", new_id); 358 | startActivity(i); 359 | } 360 | } 361 | 362 | 363 | @Override 364 | public void onBackPressed() { 365 | if (_drawer.isDrawerOpen(GravityCompat.START)) { 366 | _drawer.closeDrawer(GravityCompat.START); 367 | } else { 368 | super.onBackPressed(); 369 | } 370 | } 371 | 372 | @Override 373 | public boolean onNavigationItemSelected(@NonNull MenuItem item) { 374 | Intent intent = new Intent(); 375 | 376 | int itemId = item.getItemId(); 377 | 378 | if (itemId == R.id.home) { 379 | _drawer.closeDrawer(GravityCompat.START); 380 | return false; 381 | 382 | } else if (itemId == R.id.settings) { 383 | intent.setClass(MainActivity.this, SettingsActivity.class); 384 | 385 | } else if (itemId == R.id.about) { 386 | intent.setClass(MainActivity.this, AboutActivity.class); 387 | 388 | } else if (itemId == R.id.dc) { 389 | intent.setAction(Intent.ACTION_VIEW); 390 | intent.setData(Uri.parse("https://discord.gg/ESCfUBy26Z")); 391 | 392 | } else if (itemId == R.id.gh) { 393 | intent.setAction(Intent.ACTION_VIEW); 394 | intent.setData(Uri.parse("https://github.com/OpenBlocksTeam")); 395 | 396 | } else if (itemId == R.id.web) { 397 | intent.setAction(Intent.ACTION_VIEW); 398 | intent.setData(Uri.parse("https://openblocks.tk/")); 399 | } 400 | 401 | startActivity(intent); 402 | 403 | return false; 404 | } 405 | 406 | public static class FragmentAdapter extends FragmentStatePagerAdapter { 407 | Context context; 408 | int tabCount; 409 | 410 | HashMap> modules; 411 | ArrayList projectMetadataArrayList; 412 | 413 | public FragmentAdapter(Context context, FragmentManager fm, int tabCount, HashMap> modules, ArrayList projectMetadataArrayList) { 414 | super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); 415 | this.context = context; 416 | this.tabCount = tabCount; 417 | this.modules = modules; 418 | this.projectMetadataArrayList = projectMetadataArrayList; 419 | } 420 | 421 | @Override 422 | public int getCount() { 423 | return tabCount; 424 | } 425 | 426 | @Override 427 | public CharSequence getPageTitle(int _position) { 428 | switch (_position) { 429 | case 0: 430 | return "Projects"; 431 | case 1: 432 | return "Modules"; 433 | default: 434 | return null; 435 | } 436 | } 437 | 438 | @NonNull 439 | @Override 440 | public Fragment getItem(int _position) { 441 | switch (_position) { 442 | case 0: 443 | return ProjectsFragment.newInstance(projectMetadataArrayList); 444 | case 1: 445 | return ModulesFragment.newInstance(modules); 446 | default: 447 | return new Fragment(); 448 | } 449 | } 450 | } 451 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/ModuleConfigActivity.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.appcompat.app.ActionBar; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import android.content.Intent; 11 | import android.os.Bundle; 12 | import android.util.Log; 13 | import android.view.MenuItem; 14 | import android.widget.Toast; 15 | 16 | import com.openblocks.android.adapters.ConfigRecyclerViewAdapter; 17 | import com.openblocks.android.modman.ModuleManager; 18 | import com.openblocks.android.modman.models.Module; 19 | import com.openblocks.moduleinterface.OpenBlocksModule; 20 | import com.openblocks.moduleinterface.models.config.OpenBlocksConfig; 21 | 22 | import java.io.File; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.lang.reflect.Method; 25 | import java.util.Objects; 26 | 27 | import dalvik.system.DexClassLoader; 28 | 29 | /** 30 | * This activity is where the user can edit the module's (pre-defined by the module itself) configuration 31 | */ 32 | public class ModuleConfigActivity extends AppCompatActivity { 33 | 34 | private static final String TAG = "ModuleConfigActivity"; 35 | 36 | ConfigRecyclerViewAdapter adapter; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_module_config); 42 | 43 | // We need a back button 44 | Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); 45 | 46 | // Get the module extra 47 | Module module = getIntent().getParcelableExtra("module"); 48 | 49 | // Null check 50 | if (module == null) { 51 | Log.e(TAG, "onCreate: Module is null", null); 52 | finish(); 53 | 54 | return; 55 | } 56 | 57 | File jarfile = module.jar_file; 58 | String classpath = module.classpath; 59 | 60 | // Now load the module 61 | try { 62 | // Get the jar file's path 63 | final String libPath = jarfile.getAbsolutePath(); 64 | 65 | // Load it using DexClassLoader 66 | final DexClassLoader classloader = new DexClassLoader(libPath, getCodeCacheDir().getAbsolutePath(), null, this.getClass().getClassLoader()); 67 | final Class module_class = (Class) classloader.loadClass(classpath); 68 | 69 | // Then get the setupConfig method (one of the functions in OpenBlocks Module Interface) 70 | final Method setupConfig = module_class.getMethod("setupConfig"); 71 | 72 | // Invoke it 73 | OpenBlocksConfig config = (OpenBlocksConfig) setupConfig.invoke(module_class); 74 | 75 | // Then load it 76 | loadConfig(config); 77 | 78 | } catch (ClassNotFoundException e) { 79 | e.printStackTrace(); 80 | 81 | Toast.makeText(this, "Error while loading config: Wrong classpath: " + classpath, Toast.LENGTH_LONG).show(); 82 | } catch (IllegalAccessException e) { 83 | e.printStackTrace(); 84 | 85 | Toast.makeText(this, "IllegalAccessException: " + e.getMessage(), Toast.LENGTH_LONG).show(); 86 | } catch (NoSuchMethodException e) { 87 | e.printStackTrace(); 88 | 89 | Toast.makeText(this, "Method not found: " + e.getMessage(), Toast.LENGTH_LONG).show(); 90 | } catch (InvocationTargetException e) { 91 | e.printStackTrace(); 92 | 93 | Toast.makeText(this, "InvocationTargetException: " + e.getMessage(), Toast.LENGTH_LONG).show(); 94 | } 95 | } 96 | 97 | private void loadConfig(OpenBlocksConfig config) { 98 | // This is where we will display the config to the recyclerview, simple 99 | // Get the recyclerview 100 | RecyclerView rv = findViewById(R.id.module_config); 101 | rv.setLayoutManager(new LinearLayoutManager(this)); 102 | 103 | // And apply the adapter, done 104 | adapter = new ConfigRecyclerViewAdapter(config, this); 105 | rv.setAdapter(adapter); 106 | } 107 | 108 | @Override 109 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 110 | int id = item.getItemId(); 111 | 112 | // Did the user clicked the back button? 113 | if (id == android.R.id.home) { 114 | onBackPressed(); 115 | } 116 | 117 | return super.onOptionsItemSelected(item); 118 | } 119 | 120 | @Override 121 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 122 | super.onActivityResult(requestCode, resultCode, data); 123 | 124 | if (data == null) 125 | return; 126 | 127 | adapter.onResult(requestCode, data); 128 | } 129 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/ProjectEditorActivity.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import android.os.Bundle; 4 | import android.os.Environment; 5 | import android.util.Pair; 6 | import android.widget.Toast; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.appcompat.app.AlertDialog; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.appcompat.widget.Toolbar; 12 | import androidx.fragment.app.Fragment; 13 | import androidx.fragment.app.FragmentManager; 14 | import androidx.fragment.app.FragmentPagerAdapter; 15 | import androidx.viewpager.widget.ViewPager; 16 | 17 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 18 | import com.google.android.material.tabs.TabLayout; 19 | import com.openblocks.android.constants.IncludedBinaries; 20 | import com.openblocks.android.fragments.projecteditor.CodeEditFragment; 21 | import com.openblocks.android.fragments.projecteditor.LayoutEditFragment; 22 | import com.openblocks.android.fragments.projecteditor.LogFragment; 23 | import com.openblocks.android.helpers.BlockCollectionParser; 24 | import com.openblocks.android.modman.ModuleLoader; 25 | import com.openblocks.android.modman.ModuleLogger; 26 | import com.openblocks.android.modman.ModuleManager; 27 | import com.openblocks.android.modman.models.Module; 28 | import com.openblocks.moduleinterface.OpenBlocksModule; 29 | import com.openblocks.moduleinterface.callbacks.SaveCallback; 30 | import com.openblocks.moduleinterface.exceptions.CompileException; 31 | import com.openblocks.moduleinterface.exceptions.ParseException; 32 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 33 | import com.openblocks.moduleinterface.models.OpenBlocksRawProject; 34 | import com.openblocks.moduleinterface.models.code.ParseBlockTask; 35 | import com.openblocks.moduleinterface.models.compiler.IncludedBinary; 36 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksCode; 37 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksLayout; 38 | 39 | import java.util.ArrayList; 40 | import java.util.Arrays; 41 | import java.util.HashMap; 42 | 43 | public class ProjectEditorActivity extends AppCompatActivity { 44 | 45 | ModuleManager moduleManager = ModuleManager.getInstance(); 46 | 47 | OpenBlocksCode code; 48 | OpenBlocksLayout layout; 49 | OpenBlocksProjectMetadata metadata; 50 | 51 | ModuleLogger logger = ModuleLogger.getInstance(); 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_project_editor); 57 | 58 | // TODO: 3/10/21 Run these on an another thread / asynchronously, Project Parser might take some time to parse the project 59 | 60 | // Read the project 61 | String project_id = getIntent().getStringExtra("project_id"); 62 | 63 | // Get project manager and project parser 64 | Module project_manager = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_MANAGER); 65 | Module project_parser = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_PARSER); 66 | OpenBlocksModule.ProjectManager project_manager_instance = ModuleLoader.load(this, project_manager, OpenBlocksModule.ProjectManager.class); 67 | OpenBlocksModule.ProjectParser project_parser_instance = ModuleLoader.load(this, project_parser, OpenBlocksModule.ProjectParser.class); 68 | 69 | // Initialize these modules 70 | project_manager_instance.initialize(this, logger); 71 | project_parser_instance.initialize(this, logger); 72 | 73 | // Get the project 74 | OpenBlocksRawProject project = project_manager_instance.getProject(project_id); 75 | 76 | // Parse it 77 | try { 78 | code = project_parser_instance.parseCode(project); 79 | layout = project_parser_instance.parseLayout(project); 80 | metadata = project_parser_instance.parseMetadata(project); 81 | } catch (ParseException e) { 82 | Toast.makeText(this, "Error while parsing project: " + e.getMessage(), Toast.LENGTH_LONG).show(); 83 | 84 | return; 85 | } 86 | 87 | // After parsing the code, get the module that is used in the code 88 | Module blocks_collection_module = moduleManager.findModule(OpenBlocksModule.Type.BLOCKS_COLLECTION, code.block_collection_name); 89 | 90 | // Check if the blocks collection exists 91 | if (blocks_collection_module == null) { 92 | // Nope, there is no blocks collection with this name 93 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 94 | builder.setTitle("Error finding blocks collection"); 95 | builder.setMessage("Blocks Collection with name " + code.block_collection_name + " not found, please find the module and install it to edit this project."); 96 | builder.create().show(); 97 | 98 | finish(); 99 | } 100 | 101 | // Create some save callbacks 102 | SaveCallback code_save = code_new -> { 103 | code = code_new; 104 | 105 | // TODO: 3/10/21 Run this asynchronously 106 | 107 | // Save the code using project parser and manager 108 | project_manager_instance.saveProject( 109 | project_parser_instance.saveProject( 110 | metadata, 111 | code_new, 112 | layout 113 | ) 114 | ); 115 | 116 | Toast.makeText(this, "Code saved!", Toast.LENGTH_SHORT).show(); 117 | }; 118 | 119 | SaveCallback layout_save = layout_new -> { 120 | layout = layout_new; 121 | 122 | // TODO: 3/10/21 Run this asynchronously 123 | 124 | // Save the layout using project parser and manager 125 | project_manager_instance.saveProject( 126 | project_parser_instance.saveProject( 127 | metadata, 128 | code, 129 | layout_new 130 | ) 131 | ); 132 | 133 | Toast.makeText(this, "Layout saved!", Toast.LENGTH_SHORT).show(); 134 | }; 135 | 136 | // And bind UI elements I guess 137 | Toolbar toolbar = findViewById(R.id.project_editor_toolbar); 138 | setSupportActionBar(toolbar); 139 | 140 | Module compiler_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_COMPILER); 141 | 142 | OpenBlocksModule.BlocksCollection blocks_collection = ModuleLoader.load(this, blocks_collection_module, OpenBlocksModule.BlocksCollection.class); 143 | OpenBlocksModule.ProjectCompiler compiler = ModuleLoader.load(this, compiler_module, OpenBlocksModule.ProjectCompiler.class); 144 | 145 | IncludedBinaries.init(this); 146 | 147 | HashMap> blocks = BlockCollectionParser.parseBlocks(blocks_collection.getBlocks()); 148 | 149 | // Initialize the compiler 150 | compiler.initializeCompiler( 151 | (ArrayList) Arrays.asList(IncludedBinaries.INCLUDED_BINARIES), 152 | convertParsedBlocks(blocks) 153 | ); 154 | 155 | // Don't forget to set the blocks format for our code editor 156 | code.setBlocksFormats(convertParsedBlocks2(blocks)); 157 | 158 | FloatingActionButton run_fab = findViewById(R.id.project_editor_run); 159 | 160 | String apk_output_path = getSharedPreferences("data", MODE_PRIVATE).getString("apk_output_path", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); 161 | run_fab.setOnClickListener(view -> { 162 | try { 163 | compiler.compile(metadata, code, layout, apk_output_path); 164 | } catch (CompileException e) { 165 | e.printStackTrace(); 166 | AlertDialog.Builder builder = new AlertDialog.Builder(ProjectEditorActivity.this); 167 | builder.setTitle("An error occurred while compiling"); 168 | builder.setMessage("The compiler module (" + compiler_module.name + ") said:\n" + e.message); 169 | builder.create().show(); 170 | } 171 | }); 172 | 173 | ViewPager viewPager = findViewById(R.id.viewPager); 174 | 175 | ProjectEditorViewPagerAdapter viewPagerAdapter = new ProjectEditorViewPagerAdapter(getSupportFragmentManager(), code, layout, code_save, layout_save); 176 | viewPager.setAdapter(viewPagerAdapter); 177 | 178 | TabLayout tabLayout = findViewById(R.id.tabs); 179 | tabLayout.setupWithViewPager(viewPager); 180 | } 181 | 182 | /** 183 | * This function is used to convert the blocks list generated by 184 | * {@link BlockCollectionParser#parseBlocks(Object[])} into HashMap<\String, ParseBlockTask> 185 | * @param blocks The blocks generated from {@link BlockCollectionParser#parseBlocks(Object[])} 186 | * @return The parsed collection 187 | */ 188 | private HashMap convertParsedBlocks(HashMap> blocks) { 189 | HashMap result = new HashMap<>(); 190 | 191 | for (String key : blocks.keySet()) { 192 | result.put(key, blocks.get(key).second); 193 | } 194 | 195 | return result; 196 | } 197 | 198 | /** 199 | * This function is used to convert the blocks list generated by 200 | * {@link BlockCollectionParser#parseBlocks(Object[])} into HashMap<\String, String> 201 | * @param blocks The blocks generated from {@link BlockCollectionParser#parseBlocks(Object[])} 202 | * @return The parsed collection 203 | */ 204 | private HashMap convertParsedBlocks2(HashMap> blocks) { 205 | HashMap result = new HashMap<>(); 206 | 207 | for (String key : blocks.keySet()) { 208 | result.put(key, blocks.get(key).first); 209 | } 210 | 211 | return result; 212 | } 213 | 214 | public static class ProjectEditorViewPagerAdapter extends FragmentPagerAdapter { 215 | 216 | // TODO LOW_PRIORITY: 3/10/21 mvvm 217 | 218 | OpenBlocksCode code; 219 | OpenBlocksLayout layout; 220 | 221 | SaveCallback code_save; 222 | SaveCallback layout_save; 223 | 224 | public ProjectEditorViewPagerAdapter( 225 | FragmentManager fm, OpenBlocksCode code, OpenBlocksLayout layout, 226 | SaveCallback code_save, SaveCallback layout_save) { 227 | super(fm); 228 | this.code = code; 229 | this.layout = layout; 230 | this.code_save = code_save; 231 | this.layout_save = layout_save; 232 | } 233 | 234 | @Override 235 | public int getCount(){ 236 | return 3; 237 | } 238 | 239 | @Override 240 | public CharSequence getPageTitle(int _position) { 241 | switch (_position) { 242 | case 0: 243 | return "Layout"; 244 | case 1: 245 | return "Code"; 246 | case 2: 247 | return "Components"; 248 | default: 249 | return null; 250 | } 251 | } 252 | 253 | @NonNull 254 | @Override 255 | public Fragment getItem(int _position) { 256 | switch (_position) { 257 | case 0: 258 | return new LayoutEditFragment(layout, code, layout_save); 259 | case 1: 260 | return new CodeEditFragment(code, layout, code_save); 261 | case 2: 262 | return new LogFragment(); 263 | default: 264 | return new Fragment(); 265 | } 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/README.md: -------------------------------------------------------------------------------- 1 | ## Welcome to the source code! 2 | Here are some folders you should know: 3 | - `adapters` Is the folder where the adapters are located (Example: RecyclerViewAdapter, etc) 4 | - `constants` Is the folder where constants are located (Example: IncludedBinaries, etc) 5 | - `dialog` Is the folder where dialogs are located (Example: NewProjectDialog, etc) 6 | - `fragments` Is the folder where the fragments are located (Example: CodeEditFragment, etc) 7 | - `helpers` Is the folder where the helper classes are located, these helpers are, well, to help us (make the code a bit prettier, and simpler) 8 | - `modman` Is the folder where the module manager is located (**MOD**ule **MAN**ager). `ModuleManager.java` Is the class that is used to access, import, and remove modules. -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toolbar; 5 | 6 | import androidx.appcompat.app.ActionBar; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | 10 | public class SettingsActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.settings_activity); 16 | if (savedInstanceState == null) { 17 | getSupportFragmentManager() 18 | .beginTransaction() 19 | .replace(R.id.settings, new SettingsFragment()) 20 | .commit(); 21 | } 22 | 23 | ActionBar actionBar = getSupportActionBar(); 24 | if (actionBar != null) { 25 | actionBar.setDisplayHomeAsUpEnabled(true); 26 | } 27 | } 28 | 29 | public static class SettingsFragment extends PreferenceFragmentCompat { 30 | @Override 31 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 32 | setPreferencesFromResource(R.xml.root_preferences, rootKey); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/adapters/ConfigRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.adapters; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.text.Editable; 8 | import android.text.InputType; 9 | import android.text.TextWatcher; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.AdapterView; 14 | import android.widget.ArrayAdapter; 15 | import android.widget.Button; 16 | import android.widget.CompoundButton; 17 | import android.widget.EditText; 18 | import android.widget.FrameLayout; 19 | import android.widget.Spinner; 20 | import android.widget.Switch; 21 | import android.widget.TextView; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.appcompat.app.AlertDialog; 25 | import androidx.appcompat.widget.SwitchCompat; 26 | import androidx.recyclerview.widget.RecyclerView; 27 | 28 | import com.google.android.material.button.MaterialButton; 29 | import com.openblocks.android.ModuleConfigActivity; 30 | import com.openblocks.android.R; 31 | import com.openblocks.moduleinterface.models.config.OpenBlocksConfig; 32 | import com.openblocks.moduleinterface.models.config.OpenBlocksConfigItem; 33 | 34 | import java.lang.ref.WeakReference; 35 | import java.util.ArrayList; 36 | import java.util.HashMap; 37 | 38 | public class ConfigRecyclerViewAdapter extends RecyclerView.Adapter { 39 | 40 | OpenBlocksConfig config; 41 | 42 | // Why WeakReference? Because setting this as plain activity can cause memory leaks, the java 43 | // garbage collector doesn't collect variables that have strong reference 44 | WeakReference activity; 45 | 46 | // TAG: VALUE 47 | HashMap saved_items = new HashMap<>(); 48 | 49 | public ConfigRecyclerViewAdapter(OpenBlocksConfig configItems, Activity activity) { 50 | this.config = configItems; 51 | this.activity = new WeakReference<>(activity); 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 57 | return new ViewHolder( 58 | LayoutInflater 59 | .from(parent.getContext()) 60 | .inflate(R.layout.rv_module_item, parent, false) 61 | ); 62 | } 63 | 64 | public OpenBlocksConfig getConfig() { 65 | for (String tag: saved_items.keySet()) { 66 | config.setConfigValue(tag, saved_items.get(tag)); 67 | } 68 | 69 | return config; 70 | } 71 | 72 | public void onResult(int requestCode, Intent data) { 73 | // Set the value as the path 74 | saved_items.put(config.getTAGs()[requestCode], data.getData().getPath()); 75 | } 76 | 77 | @Override 78 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 79 | OpenBlocksConfigItem item = config.getConfigs().get(position); 80 | String[] tags = config.getTAGs(); 81 | 82 | Context context = holder.title.getContext(); 83 | View input = null; 84 | 85 | holder.title.setText(item.text); 86 | 87 | switch (item.config_type) { 88 | case SWITCH: 89 | SwitchCompat switch_ = new SwitchCompat(context); 90 | switch_.setShowText(false); 91 | switch_.setChecked((Boolean) item.value); 92 | 93 | switch_.setOnCheckedChangeListener((buttonView, isChecked) -> { 94 | saved_items.put(tags[position], isChecked); 95 | }); 96 | 97 | input = switch_; 98 | break; 99 | 100 | case DROPDOWN: 101 | Spinner spinner = new Spinner(context); 102 | spinner.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_spinner_dropdown_item, (ArrayList) item.extra)); 103 | 104 | spinner.setOnItemClickListener((parent, view, position1, id) -> 105 | saved_items.put( 106 | tags[position], 107 | ((String[]) item.extra)[position1] 108 | ) 109 | ); 110 | 111 | input = spinner; 112 | break; 113 | 114 | case OPEN_FILE: 115 | MaterialButton button = new MaterialButton(context); 116 | button.setText("Open File"); 117 | 118 | button.setOnClickListener(v -> { 119 | Intent i = new Intent(); 120 | i .setAction(Intent.ACTION_OPEN_DOCUMENT) 121 | .addCategory(Intent.CATEGORY_OPENABLE); 122 | 123 | activity.get().startActivityForResult(i, position); // Use the position as the request code 124 | }); 125 | 126 | input = button; 127 | break; 128 | 129 | case INPUT_TEXT: 130 | EditText editText = new EditText(context); 131 | editText.setText((String) item.value); 132 | editText.setHint("Input here"); 133 | 134 | editText.addTextChangedListener(new TextWatcher() { 135 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 136 | @Override public void afterTextChanged(Editable s) { } 137 | 138 | @Override 139 | public void onTextChanged(CharSequence s, int start, int before, int count) { 140 | saved_items.put(tags[position], s.toString()); 141 | } 142 | }); 143 | 144 | input = editText; 145 | break; 146 | 147 | case INPUT_NUMBER: 148 | EditText editText_ = new EditText(context); 149 | editText_.setRawInputType(InputType.TYPE_CLASS_NUMBER); 150 | editText_.setText((String) item.value); 151 | editText_.setHint("Input here"); 152 | 153 | editText_.addTextChangedListener(new TextWatcher() { 154 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 155 | @Override public void afterTextChanged(Editable s) { } 156 | 157 | @Override 158 | public void onTextChanged(CharSequence s, int start, int before, int count) { 159 | saved_items.put(tags[position], Integer.parseInt(s.toString())); 160 | } 161 | }); 162 | 163 | input = editText_; 164 | break; 165 | 166 | case MULTIPLE_CHOICE: 167 | // https://stackoverflow.com/questions/40700140/how-to-initialize-a-multiple-choice-alert-dialog-with-every-option-selected 168 | String[] items = (String[]) item.value; 169 | ArrayList results = new ArrayList<>(); 170 | 171 | boolean[] checkedItems = new boolean[items.length]; 172 | 173 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 174 | builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> { 175 | if (isChecked && !results.contains(items[which])) { 176 | results.add(items[which]); 177 | 178 | } else if (results.contains(items[which])) { 179 | results.remove(which); 180 | } 181 | 182 | }).setPositiveButton("OK", (dialog, id) -> 183 | saved_items.put(tags[position], results) 184 | 185 | ).setNegativeButton("Cancel", (dialog, id) -> 186 | dialog.dismiss() 187 | ); 188 | 189 | break; 190 | 191 | default: 192 | break; 193 | } 194 | 195 | holder.holder.addView(input); 196 | } 197 | 198 | @Override 199 | public int getItemCount() { 200 | return config.getConfigs().size(); 201 | } 202 | 203 | public static class ViewHolder extends RecyclerView.ViewHolder { 204 | 205 | TextView title; 206 | 207 | FrameLayout holder; 208 | 209 | public ViewHolder(@NonNull View itemView) { 210 | super(itemView); 211 | title = itemView.findViewById(R.id.config_title); 212 | holder = itemView.findViewById(R.id.input_holder); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/adapters/ModulesRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.adapters; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.res.Resources; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ImageView; 13 | import android.widget.TextView; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.core.content.res.ResourcesCompat; 17 | import androidx.recyclerview.widget.RecyclerView; 18 | 19 | import com.openblocks.android.ModuleConfigActivity; 20 | import com.openblocks.android.R; 21 | import com.openblocks.android.modman.ModuleManager; 22 | import com.openblocks.android.modman.models.Module; 23 | import com.openblocks.moduleinterface.OpenBlocksModule; 24 | 25 | import java.lang.ref.WeakReference; 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | 29 | public class ModulesRecyclerViewAdapter extends RecyclerView.Adapter { 30 | private static final String TAG = "ModuleRVAdapter"; 31 | WeakReference activity; 32 | // TODO: REPLACE THIS WITH JUST MODULE MANAGER 33 | private HashMap> data = new HashMap<>(); 34 | private ArrayList modules; 35 | private ModuleManager moduleManager; 36 | 37 | public ModulesRecyclerViewAdapter(Activity activity) { 38 | this.activity = new WeakReference<>(activity); 39 | } 40 | 41 | public ModulesRecyclerViewAdapter(HashMap> data, Activity activity) { 42 | this.data = data; 43 | this.activity = new WeakReference<>(activity); 44 | 45 | moduleManager = ModuleManager.getInstance(); 46 | modules = moduleManager.getModulesAsList(); 47 | } 48 | 49 | public void updateView(HashMap> data) { 50 | this.data = data; 51 | notifyDataSetChanged(); 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 57 | return new ViewHolder( 58 | LayoutInflater 59 | .from(parent.getContext()) 60 | .inflate(R.layout.rv_module_item, parent, false) 61 | ); 62 | } 63 | 64 | @SuppressLint("SetTextI18n") 65 | @Override 66 | public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { 67 | Log.d(TAG, "onBindViewHolder: called."); 68 | Module item = modules.get(position); 69 | 70 | holder.name.setText(item.name); 71 | holder.description.setText(item.description); 72 | 73 | // Check if this module is active 74 | if (item.equals(moduleManager.getActiveModule(item.module_type))) { 75 | // Nop it's inactive, red text with NOT ACTIVE text 76 | holder.active_status.setText("NOT ACTIVE"); 77 | holder.active_status.setTextColor(0xFFE61212); 78 | } 79 | 80 | Context context = activity.get(); 81 | Resources resources = context.getResources(); 82 | Resources.Theme theme = context.getTheme(); 83 | 84 | switch (item.module_type) { 85 | case PROJECT_MANAGER: 86 | holder.module_type.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_project_manager, theme)); 87 | break; 88 | case PROJECT_PARSER: 89 | holder.module_type.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_project_parser, theme)); 90 | break; 91 | case PROJECT_LAYOUT_GUI: 92 | holder.module_type.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_layout, theme)); 93 | break; 94 | case PROJECT_CODE_GUI: 95 | holder.module_type.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_code, theme)); 96 | break; 97 | case PROJECT_COMPILER: 98 | holder.module_type.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_compiler, theme)); 99 | break; 100 | } 101 | 102 | holder.body.setOnClickListener(v -> { 103 | Intent open_config = new Intent(activity.get(), ModuleConfigActivity.class); 104 | open_config.putExtra("module", item); 105 | activity.get().startActivity(open_config); 106 | }); 107 | } 108 | 109 | @Override 110 | public int getItemCount() { 111 | if (data == null) return 0; 112 | 113 | int sum = 0; 114 | 115 | for (ArrayList modules : data.values()) { 116 | if (modules == null) continue; 117 | sum += modules.size(); 118 | } 119 | 120 | return sum; 121 | } 122 | 123 | public static class ViewHolder extends RecyclerView.ViewHolder { 124 | 125 | TextView name; 126 | TextView description; 127 | TextView active_status; 128 | 129 | ImageView module_type; 130 | 131 | View body; 132 | 133 | public ViewHolder(@NonNull View itemView) { 134 | super(itemView); 135 | name = itemView.findViewById(R.id.module_title); 136 | description = itemView.findViewById(R.id.module_description); 137 | module_type = itemView.findViewById(R.id.module_type); 138 | active_status = itemView.findViewById(R.id.module_active_status); 139 | 140 | body = itemView.findViewById(R.id.module_body); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/adapters/ProjectRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.adapters; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | import com.openblocks.android.R; 12 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 13 | 14 | import java.util.ArrayList; 15 | 16 | public class ProjectRecyclerViewAdapter extends RecyclerView.Adapter { 17 | 18 | ArrayList projects_metadata; 19 | 20 | public ProjectRecyclerViewAdapter(ArrayList projects_metadata) { 21 | this.projects_metadata = projects_metadata; 22 | } 23 | 24 | @NonNull 25 | @Override 26 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 27 | return new ViewHolder( 28 | LayoutInflater.from(parent.getContext()) 29 | .inflate(R.layout.rv_project_item, parent) 30 | ); 31 | } 32 | 33 | @Override 34 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 35 | OpenBlocksProjectMetadata metadata = projects_metadata.get(position); 36 | 37 | holder.project_title.setText(metadata.getName()); 38 | holder.project_package.setText(metadata.getPackageName()); 39 | } 40 | 41 | @Override 42 | public int getItemCount() { 43 | return projects_metadata.size(); 44 | } 45 | 46 | static class ViewHolder extends RecyclerView.ViewHolder { 47 | 48 | TextView project_title; 49 | TextView project_package; 50 | 51 | public ViewHolder(@NonNull View itemView) { 52 | super(itemView); 53 | 54 | project_title = itemView.findViewById(R.id.project_title); 55 | project_package = itemView.findViewById(R.id.project_package); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/constants/IncludedBinaries.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.constants; 2 | 3 | import android.content.Context; 4 | 5 | import com.openblocks.moduleinterface.models.compiler.IncludedBinary; 6 | 7 | public class IncludedBinaries { 8 | public static IncludedBinary[] INCLUDED_BINARIES; 9 | 10 | public static void init(Context context) { 11 | String nativeLibraryDir = context.getApplicationInfo().nativeLibraryDir; 12 | 13 | INCLUDED_BINARIES = new IncludedBinary[] { 14 | new IncludedBinary( 15 | "aapt", 16 | 1, 17 | nativeLibraryDir.concat("/bin/aapt") 18 | ), 19 | new IncludedBinary( 20 | "aapt2", 21 | 1, 22 | nativeLibraryDir.concat("/bin/aapt2") 23 | ), 24 | new IncludedBinary( 25 | "zipalign", 26 | 1, 27 | nativeLibraryDir.concat("/bin/zipalign") 28 | ), 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/dialog/NewProjectDialog.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.dialog; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 8 | import com.openblocks.moduleinterface.models.OpenBlocksRawProject; 9 | 10 | import java.util.ArrayList; 11 | 12 | public class NewProjectDialog extends ProjectMetadataEditDialog { 13 | 14 | public NewProjectDialog(@NonNull Context context, String newProjectId) { 15 | super(context, new OpenBlocksProjectMetadata("", "", "", 1), newProjectId); 16 | 17 | projectId = newProjectId; 18 | titleText = "New project"; 19 | buttonText = "Create project"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/dialog/ProjectMetadataEditDialog.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.dialog; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.appcompat.app.AlertDialog; 8 | 9 | import com.google.android.material.button.MaterialButton; 10 | import com.google.android.material.textfield.TextInputEditText; 11 | import com.openblocks.android.databinding.DialogEditProjectMetadataBinding; 12 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 13 | 14 | public class ProjectMetadataEditDialog extends AlertDialog { 15 | 16 | protected final TextView title; 17 | protected final TextInputEditText appName; 18 | protected final TextInputEditText packageName; 19 | protected final TextInputEditText versionName; 20 | protected final TextInputEditText versionCode; 21 | protected final MaterialButton saveButton; 22 | protected String projectId; 23 | 24 | // TODO: Set title to "Edit " 25 | 26 | protected String titleText = "Edit"; 27 | protected String buttonText = "Save project"; 28 | 29 | protected OnMetadataSavedListener listener; 30 | 31 | public ProjectMetadataEditDialog(@NonNull Context context, OpenBlocksProjectMetadata metadata, String project_id) { 32 | super(context); 33 | projectId = project_id; 34 | 35 | DialogEditProjectMetadataBinding binding = DialogEditProjectMetadataBinding.inflate(getLayoutInflater()); 36 | setView(binding.getRoot()); 37 | 38 | title = binding.textView2; 39 | appName = binding.editAppName; 40 | packageName = binding.editPackage; 41 | versionName = binding.editVersionName; 42 | versionCode = binding.editVersionCode; 43 | saveButton = binding.okButton; 44 | 45 | // Set these values 46 | appName.setText(metadata.getName()); 47 | packageName.setText(metadata.getPackageName()); 48 | versionName.setText(metadata.getVersionName()); 49 | versionCode.setText(metadata.getVersionCode()); 50 | } 51 | 52 | @Override 53 | protected void onStart() { 54 | super.onStart(); 55 | 56 | title.setText(titleText); 57 | saveButton.setText(buttonText); 58 | 59 | saveButton.setOnClickListener(v -> { 60 | if (!validateInput()) return; 61 | 62 | listener.onMetadataSaved( 63 | appName.getText().toString(), 64 | packageName.getText().toString(), 65 | versionName.getText().toString(), 66 | Integer.parseInt(versionCode.getText().toString()) 67 | ); 68 | 69 | dismiss(); 70 | }); 71 | } 72 | 73 | /** 74 | * @param Any class extending {@link ProjectMetadataEditDialog}. 75 | * @param listener The {@link OnMetadataSavedListener} object to register as callback for this dialog instance. 76 | * @return The current {@link ProjectMetadataEditDialog}. 77 | */ 78 | public T addOnMetadataSavedListener(OnMetadataSavedListener listener) { 79 | this.listener = listener; 80 | return (T) this; 81 | } 82 | 83 | /** 84 | * Validates input of all TextInputEditTexts in this dialog. If a text field has invalid data, 85 | * an error is set, if not, its error gets cleared. 86 | * 87 | * @return If the input in the TextInputEditTexts is valid. 88 | */ 89 | protected boolean validateInput() { 90 | if (appName.getText().toString().isEmpty()) { 91 | appName.setError("Missing app name"); 92 | } else { 93 | appName.setError(null); 94 | } 95 | 96 | if (packageName.getText().toString().isEmpty()) { 97 | packageName.setError("Missing package name"); 98 | } else { 99 | packageName.setError(null); 100 | } 101 | 102 | if (versionName.getText().toString().isEmpty()) { 103 | versionName.setError("Missing version name"); 104 | } else { 105 | versionName.setError(null); 106 | } 107 | 108 | if (versionCode.getText().toString().isEmpty()) { 109 | versionCode.setError("Missing version code"); 110 | } else { 111 | versionCode.setError(null); 112 | } 113 | 114 | return appName.getError() == null && packageName.getError() == null 115 | && versionName.getError() == null && versionCode.getError() == null; 116 | } 117 | 118 | /** 119 | * A listener class used with a {@link ProjectMetadataEditDialog} class, providing an event 120 | * when the metadata for a project has been saved. 121 | */ 122 | public interface OnMetadataSavedListener { 123 | /** 124 | * Called when metadata for the project has been saved. 125 | * 126 | * @param appName The project's new application name. 127 | * @param packageName The project's new APK package name. 128 | * @param versionName The project's new version name. 129 | * @param versionCode The project's new version code. 130 | */ 131 | void onMetadataSaved(String appName, String packageName, String versionName, int versionCode); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/fragments/main/ModulesFragment.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.fragments.main; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.Fragment; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 8 | 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.openblocks.android.R; 14 | import com.openblocks.android.adapters.ModulesRecyclerViewAdapter; 15 | import com.openblocks.android.modman.models.Module; 16 | import com.openblocks.moduleinterface.OpenBlocksModule; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | 21 | /** 22 | * A simple {@link Fragment} subclass. 23 | * Use the {@link ModulesFragment#newInstance} factory method to 24 | * create an instance of this fragment. 25 | */ 26 | public class ModulesFragment extends Fragment { 27 | 28 | // TODO: MVVM 29 | 30 | // Every modules (unsorted), this is used to display the recyclerview 31 | HashMap> modules; 32 | 33 | public ModulesFragment() { 34 | // Required empty public constructor 35 | } 36 | 37 | /** 38 | * Use this factory method to create a new instance of 39 | * this fragment using the provided parameters. 40 | * 41 | * @param modules List of Modules 42 | * @return A new instance of fragment ModulesFragment. 43 | */ 44 | // TODO: Rename and change types and number of parameters 45 | public static ModulesFragment newInstance(HashMap> modules) { 46 | ModulesFragment fragment = new ModulesFragment(); 47 | 48 | Bundle args = new Bundle(); 49 | args.putParcelableArrayList("PROJECT_MANAGER", modules.get(OpenBlocksModule.Type.PROJECT_MANAGER)); 50 | args.putParcelableArrayList("PROJECT_PARSER", modules.get(OpenBlocksModule.Type.PROJECT_PARSER)); 51 | args.putParcelableArrayList("PROJECT_CODE_GUI", modules.get(OpenBlocksModule.Type.PROJECT_CODE_GUI)); 52 | args.putParcelableArrayList("PROJECT_LAYOUT_GUI", modules.get(OpenBlocksModule.Type.PROJECT_LAYOUT_GUI)); 53 | args.putParcelableArrayList("PROJECT_COMPILER", modules.get(OpenBlocksModule.Type.PROJECT_COMPILER)); 54 | fragment.setArguments(args); 55 | 56 | return fragment; 57 | } 58 | 59 | @Override 60 | public void onCreate(Bundle savedInstanceState) { 61 | super.onCreate(savedInstanceState); 62 | if (getArguments() != null) { 63 | modules = new HashMap<>(); 64 | 65 | modules.put(OpenBlocksModule.Type.PROJECT_MANAGER, getArguments().getParcelableArrayList("PROJECT_MANAGER")); 66 | modules.put(OpenBlocksModule.Type.PROJECT_PARSER, getArguments().getParcelableArrayList("PROJECT_PARSER")); 67 | modules.put(OpenBlocksModule.Type.PROJECT_CODE_GUI, getArguments().getParcelableArrayList("PROJECT_CODE_GUI")); 68 | modules.put(OpenBlocksModule.Type.PROJECT_LAYOUT_GUI, getArguments().getParcelableArrayList("PROJECT_LAYOUT_GUI")); 69 | modules.put(OpenBlocksModule.Type.PROJECT_COMPILER, getArguments().getParcelableArrayList("PROJECT_COMPILER")); 70 | } 71 | } 72 | 73 | @Override 74 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 75 | Bundle savedInstanceState) { 76 | // Inflate the layout for this fragment 77 | View root = inflater.inflate(R.layout.fragment_modules, container, false); 78 | 79 | RecyclerView modules_list = root.findViewById(R.id.modules_list); 80 | modules_list.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); 81 | 82 | ModulesRecyclerViewAdapter adapter = new ModulesRecyclerViewAdapter(modules, requireActivity()); 83 | modules_list.setAdapter(adapter); 84 | 85 | return root; 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/fragments/main/ProjectsFragment.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.fragments.main; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.Fragment; 6 | import androidx.recyclerview.widget.LinearLayoutManager; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.openblocks.android.R; 14 | import com.openblocks.android.adapters.ProjectRecyclerViewAdapter; 15 | import com.openblocks.moduleinterface.models.OpenBlocksProjectMetadata; 16 | 17 | import java.util.ArrayList; 18 | 19 | /** 20 | * A simple {@link Fragment} subclass. 21 | * Use the {@link ProjectsFragment#newInstance} factory method to 22 | * create an instance of this fragment. 23 | */ 24 | public class ProjectsFragment extends Fragment { 25 | 26 | ArrayList projectMetadata; 27 | 28 | public ProjectsFragment() { 29 | // Required empty public constructor 30 | } 31 | 32 | /** 33 | * Use this factory method to create a new instance of 34 | * this fragment using the provided parameters. 35 | * 36 | * @return A new instance of fragment ProjectsFragment. 37 | */ 38 | public static ProjectsFragment newInstance(ArrayList projectMetadata) { 39 | ProjectsFragment fragment = new ProjectsFragment(); 40 | Bundle args = new Bundle(); 41 | args.putParcelableArrayList("projects_metadata", projectMetadata); 42 | fragment.setArguments(args); 43 | return fragment; 44 | } 45 | 46 | @Override 47 | public void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | 50 | if (getArguments() != null) { 51 | this.projectMetadata = getArguments().getParcelableArrayList("projects_metadata"); 52 | } 53 | } 54 | 55 | @Override 56 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 57 | Bundle savedInstanceState) { 58 | // Inflate the layout for this fragment 59 | View root = inflater.inflate(R.layout.fragment_projects, container, false); 60 | 61 | RecyclerView projects = root.findViewById(R.id.projectRecyclerView); 62 | ProjectRecyclerViewAdapter adapter = new ProjectRecyclerViewAdapter(projectMetadata); 63 | projects.setLayoutManager(new LinearLayoutManager(requireContext())); 64 | projects.setAdapter(adapter); 65 | 66 | return root; 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/fragments/projecteditor/CodeEditFragment.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.fragments.projecteditor; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.Fragment; 6 | 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.openblocks.android.R; 12 | import com.openblocks.android.modman.ModuleLoader; 13 | import com.openblocks.android.modman.ModuleManager; 14 | import com.openblocks.android.modman.models.Module; 15 | import com.openblocks.moduleinterface.OpenBlocksModule; 16 | import com.openblocks.moduleinterface.callbacks.SaveCallback; 17 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksCode; 18 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksLayout; 19 | 20 | public class CodeEditFragment extends Fragment { 21 | 22 | OpenBlocksCode code; 23 | OpenBlocksLayout layout; 24 | SaveCallback code_save; 25 | OpenBlocksModule.ProjectCodeGUI module_instance; 26 | 27 | public CodeEditFragment(OpenBlocksCode code, OpenBlocksLayout layout, SaveCallback code_save) { 28 | this.code = code; 29 | this.layout = layout; 30 | this.code_save = code_save; 31 | } 32 | 33 | @Override 34 | public void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | 37 | ModuleManager moduleManager = ModuleManager.getInstance(); 38 | Module code_ui_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_CODE_GUI); 39 | module_instance = ModuleLoader.load(requireContext(), code_ui_module, OpenBlocksModule.ProjectCodeGUI.class); 40 | } 41 | 42 | @Override 43 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 44 | Bundle savedInstanceState) { 45 | // Inflate the layout for this fragment 46 | View root = inflater.inflate(R.layout.fragment_code_edit, container, false); 47 | module_instance.show(requireContext(), root.findViewById(R.id.code_edit_parent), code, layout, code_save); 48 | return root; 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/fragments/projecteditor/LayoutEditFragment.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.fragments.projecteditor; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.Fragment; 6 | 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.openblocks.android.R; 12 | import com.openblocks.android.modman.ModuleLoader; 13 | import com.openblocks.android.modman.ModuleManager; 14 | import com.openblocks.android.modman.models.Module; 15 | import com.openblocks.moduleinterface.OpenBlocksModule; 16 | import com.openblocks.moduleinterface.callbacks.SaveCallback; 17 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksCode; 18 | import com.openblocks.moduleinterface.projectfiles.OpenBlocksLayout; 19 | 20 | public class LayoutEditFragment extends Fragment { 21 | 22 | OpenBlocksCode code; 23 | OpenBlocksLayout layout; 24 | SaveCallback layout_save; 25 | OpenBlocksModule.ProjectLayoutGUI module_instance; 26 | 27 | public LayoutEditFragment(OpenBlocksLayout layout, OpenBlocksCode code, SaveCallback code_save) { 28 | this.layout = layout; 29 | this.code = code; 30 | this.layout_save = code_save; 31 | } 32 | 33 | @Override 34 | public void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | 37 | ModuleManager moduleManager = ModuleManager.getInstance(); 38 | Module layout_ui_module = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_LAYOUT_GUI); 39 | module_instance = ModuleLoader.load(requireContext(), layout_ui_module, OpenBlocksModule.ProjectLayoutGUI.class); 40 | } 41 | 42 | @Override 43 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 44 | Bundle savedInstanceState) { 45 | // Inflate the layout for this fragment 46 | View root = inflater.inflate(R.layout.fragment_code_edit, container, false); 47 | module_instance.show(requireContext(), root.findViewById(R.id.code_edit_parent), code, layout, layout_save); 48 | return root; 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/fragments/projecteditor/LogFragment.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.fragments.projecteditor; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.Fragment; 6 | 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.openblocks.android.R; 13 | import com.openblocks.android.modman.ModuleLogger; 14 | 15 | // Component is not planned yet, It's currently used just for a placeholder 16 | public class LogFragment extends Fragment { 17 | 18 | ModuleLogger logger = ModuleLogger.getInstance(); 19 | 20 | public LogFragment() { } 21 | 22 | @Override 23 | public void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | } 26 | 27 | @Override 28 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 29 | Bundle savedInstanceState) { 30 | // Inflate the layout for this fragment 31 | View root = inflater.inflate(R.layout.fragment_log, container, false); 32 | 33 | // Every time the log is updated, our log text should also be updated 34 | logger.setLiveLog(log -> { 35 | TextView log_text = root.findViewById(R.id.log_text); 36 | log_text.setText(log); 37 | }); 38 | 39 | root.findViewById(R.id.clear_log_button).setOnClickListener(v -> logger.clearLog()); 40 | 41 | return root; 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/helpers/BlockCollectionParser.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.helpers; 2 | 3 | import android.util.Pair; 4 | 5 | import com.openblocks.moduleinterface.OpenBlocksModule; 6 | import com.openblocks.moduleinterface.models.code.ParseBlockTask; 7 | 8 | import java.util.HashMap; 9 | 10 | public class BlockCollectionParser { 11 | 12 | /** 13 | * This function is used to parse blocks that are set by BlocksCollection module 14 | * @param blocks The blocks got from {@link OpenBlocksModule.BlocksCollection#getBlocks()} or something like this: Object[Object[String opcode, String format, ParseBlockTask]] 15 | * @return A HashMap of opcode, Pair of format and ParseBlockTask 16 | */ 17 | public static HashMap> parseBlocks(Object[] blocks) { 18 | // Object[Object[String opcode, String format, ParseBlockTask]] 19 | 20 | HashMap> result = new HashMap<>(); 21 | 22 | for (Object block_object : blocks) { 23 | Object[] block = (Object[]) block_object; 24 | 25 | String opcode = (String) block[0]; 26 | String format = (String) block[1]; 27 | ParseBlockTask parseBlockTask = (ParseBlockTask) block[2]; 28 | 29 | result.put(opcode, new Pair<>(format, parseBlockTask)); 30 | } 31 | 32 | return result; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/helpers/FileHelper.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.helpers; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.core.util.Pair; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.DataInputStream; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.util.LinkedList; 16 | 17 | public class FileHelper { 18 | 19 | @NonNull 20 | public static String readFile(File file) throws IOException { 21 | return readFile(file.getAbsolutePath()); 22 | } 23 | 24 | // Copied from: https://www.journaldev.com/9400/android-external-storage-read-write-save-file 25 | @NonNull 26 | public static String readFile(String path) throws IOException { 27 | StringBuilder output = new StringBuilder(); 28 | FileInputStream fis = new FileInputStream(path); 29 | DataInputStream in = new DataInputStream(fis); 30 | BufferedReader br = 31 | new BufferedReader(new InputStreamReader(in)); 32 | String strLine; 33 | while ((strLine = br.readLine()) != null) { 34 | output.append(strLine); 35 | } 36 | in.close(); 37 | return output.toString(); 38 | } 39 | 40 | public static byte[] readFile(final FileInputStream stream) { 41 | class Reader extends Thread { 42 | byte[] array = null; 43 | } 44 | 45 | Reader reader = new Reader() { 46 | public void run() { 47 | LinkedList> chunks = new LinkedList<>(); 48 | 49 | // read the file and build chunks 50 | int size = 0; 51 | int globalSize = 0; 52 | do { 53 | try { 54 | int chunkSize = 8192; 55 | // read chunk 56 | byte[] buffer = new byte[chunkSize]; 57 | size = stream.read(buffer, 0, chunkSize); 58 | if (size > 0) { 59 | globalSize += size; 60 | 61 | // add chunk to list 62 | chunks.add(new Pair<>(buffer, size)); 63 | } 64 | } catch (Exception e) { 65 | // very bad 66 | } 67 | } while (size > 0); 68 | 69 | try { 70 | stream.close(); 71 | } catch (Exception e) { 72 | // very bad 73 | } 74 | 75 | array = new byte[globalSize]; 76 | 77 | // append all chunks to one array 78 | int offset = 0; 79 | for (Pair chunk : chunks) { 80 | // flush chunk to array 81 | System.arraycopy(chunk.first, 0, array, offset, chunk.second); 82 | offset += chunk.second; 83 | } 84 | } 85 | }; 86 | 87 | reader.start(); 88 | try { 89 | reader.join(); 90 | } catch (InterruptedException e) { 91 | Log.e("Util", "Failed on reading file from storage while the locking Thread", e); 92 | return null; 93 | } 94 | 95 | return reader.array; 96 | } 97 | 98 | public static void writeFile(File file, byte[] data) throws IOException { 99 | if (!file.exists()) { 100 | file.getParentFile().mkdirs(); 101 | file.createNewFile(); 102 | } 103 | FileOutputStream outputStream = new FileOutputStream(file); 104 | outputStream.write(data); 105 | outputStream.flush(); 106 | outputStream.close(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/modman/ModuleJsonCorruptedException.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.modman; 2 | 3 | // This exception will be thrown if the modules.json is corrupted 4 | public class ModuleJsonCorruptedException extends Exception { 5 | public ModuleJsonCorruptedException(String message) { super(message); } 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/modman/ModuleLoader.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.modman; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | import com.openblocks.android.modman.models.Module; 7 | import com.openblocks.moduleinterface.OpenBlocksModule; 8 | 9 | /** 10 | * ModuleLoader is a utility class used to load modules into OpenBlocksModule 11 | */ 12 | public class ModuleLoader { 13 | 14 | @SuppressWarnings("unchecked") 15 | // TODO: 3/23/21 restore this 16 | public static T load(Context context, Module module, Class type) { 17 | ModuleManager moduleManager = ModuleManager.getInstance(); 18 | 19 | Module project_manager = moduleManager.getActiveModule(OpenBlocksModule.Type.PROJECT_MANAGER); 20 | Class project_manager_class; 21 | 22 | try { 23 | project_manager_class = moduleManager.fetchModule(context, project_manager); 24 | 25 | return (T) project_manager_class.newInstance(); 26 | } catch (ClassNotFoundException e) { 27 | e.printStackTrace(); 28 | 29 | Toast.makeText(context, "Error whilst loading project manager module " + project_manager.name + ": Wrong classpath", Toast.LENGTH_LONG).show(); 30 | } catch (IllegalAccessException | InstantiationException e) { 31 | e.printStackTrace(); 32 | 33 | Toast.makeText(context, "Error while instantiating module " + project_manager.name + ": " + e.getMessage(), Toast.LENGTH_LONG).show(); 34 | } catch (NullPointerException e) { 35 | e.printStackTrace(); 36 | 37 | Toast.makeText(context, "Error while accessing a module: " + e.getMessage(), Toast.LENGTH_SHORT).show(); 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/modman/ModuleLogger.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.modman; 2 | 3 | import android.text.Spannable; 4 | import android.text.SpannableString; 5 | import android.text.SpannableStringBuilder; 6 | import android.text.Spanned; 7 | import android.text.style.ForegroundColorSpan; 8 | import android.text.style.StyleSpan; 9 | 10 | import com.openblocks.moduleinterface.OpenBlocksModule; 11 | import com.openblocks.moduleinterface.callbacks.Logger; 12 | 13 | public class ModuleLogger implements Logger { 14 | 15 | static ModuleLogger logger; 16 | 17 | public static ModuleLogger getInstance() { 18 | if (logger == null) { 19 | logger = new ModuleLogger(); 20 | } 21 | 22 | return logger; 23 | } 24 | 25 | SpannableStringBuilder log; 26 | Class class_before; 27 | 28 | LiveLog liveLog = log -> { }; 29 | 30 | 31 | @Override 32 | public void debug(Class module_class, String text) { 33 | log("DEBUG", module_class, text, 0xFF999999, false); 34 | } 35 | 36 | @Override 37 | public void trace(Class module_class, String text) { 38 | log("TRACE", module_class, text, 0xFFFFDB5A, false); 39 | } 40 | 41 | @Override 42 | public void info(Class module_class, String text) { 43 | log("INFO", module_class, text, 0xFF000000, false); 44 | } 45 | 46 | @Override 47 | public void warn(Class module_class, String text) { 48 | log("WARN", module_class, text, 0XFFFFAD1F, false); 49 | } 50 | 51 | @Override 52 | public void err(Class module_class, String text) { 53 | log("ERROR", module_class, text, 0xFFF31B1B, false); 54 | } 55 | 56 | @Override 57 | public void fatal(Class module_class, String text) { 58 | log("FATAL", module_class, text, 0xFFC80D0D, true); 59 | } 60 | 61 | 62 | private void log(String log_level, Class module_class, String log_text, int color, boolean bold) { 63 | String text = getLogStart(module_class) + " [" + log_level + "]: " + log_text; 64 | int start = log.length(); 65 | 66 | log.append( 67 | text, 68 | new ForegroundColorSpan(color), 69 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 70 | ); 71 | 72 | if (bold) 73 | log.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, log.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 74 | 75 | 76 | // TODO: 3/21/21 Because some logs might be bit spammy, we might should optimize 77 | // so the UI doesn't freeze if there are soo many log calls 78 | liveLog.onLogChange(getText()); 79 | } 80 | 81 | 82 | private String getLogStart(Class clazz) { 83 | // TODO: 3/21/21 Because the module might implement other interfaces, 84 | // we should just get classes that extends OpenBlocksModule 85 | return clazz.getInterfaces()[0].getName() + " " + clazz.getName(); 86 | } 87 | 88 | public SpannableString getText() { 89 | return SpannableString.valueOf(log); 90 | } 91 | 92 | public void clearLog() { 93 | log.clear(); 94 | } 95 | 96 | public void setLiveLog(LiveLog liveLog) { 97 | this.liveLog = liveLog; 98 | this.liveLog.onLogChange(getText()); 99 | } 100 | 101 | public interface LiveLog { 102 | void onLogChange(SpannableString log); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/openblocks/android/modman/models/Module.java: -------------------------------------------------------------------------------- 1 | package com.openblocks.android.modman.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.openblocks.moduleinterface.OpenBlocksModule; 7 | import com.openblocks.moduleinterface.models.config.OpenBlocksConfig; 8 | 9 | import java.io.File; 10 | 11 | public class Module implements Parcelable { 12 | public String name; 13 | public String description; 14 | public String classpath; 15 | public String filename; 16 | 17 | public int version; 18 | public int lib_version; 19 | 20 | public File jar_file; 21 | 22 | public OpenBlocksModule.Type module_type; 23 | 24 | public Module() { } 25 | 26 | public Module(String filename, String name, String description, String classpath, int version, int lib_version, File jar_file, OpenBlocksModule.Type module_type) { 27 | this.filename = filename; 28 | this.name = name; 29 | this.description = description; 30 | this.classpath = classpath; 31 | this.version = version; 32 | this.lib_version = lib_version; 33 | this.jar_file = jar_file; 34 | this.module_type = module_type; 35 | } 36 | 37 | protected Module(Parcel in) { 38 | name = in.readString(); 39 | description = in.readString(); 40 | classpath = in.readString(); 41 | version = in.readInt(); 42 | lib_version = in.readInt(); 43 | jar_file = new File(in.readString()); 44 | module_type = OpenBlocksModule.Type.valueOf(in.readString()); 45 | } 46 | 47 | public static final Creator CREATOR = new Creator() { 48 | @Override 49 | public Module createFromParcel(Parcel in) { 50 | return new Module(in); 51 | } 52 | 53 | @Override 54 | public Module[] newArray(int size) { 55 | return new Module[size]; 56 | } 57 | }; 58 | 59 | @Override 60 | public int describeContents() { 61 | return 0; 62 | } 63 | 64 | @Override 65 | public void writeToParcel(Parcel dest, int flags) { 66 | dest.writeString(name); 67 | dest.writeString(description); 68 | dest.writeString(classpath); 69 | dest.writeInt(version); 70 | dest.writeInt(lib_version); 71 | dest.writeString(jar_file.getAbsolutePath()); 72 | dest.writeString(module_type.toString()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_about.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_add_white.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_discord.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_home.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_website.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_add_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_add_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_discord.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-hdpi/ic_website.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_add_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_add_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_discord.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-mdpi/ic_website.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_add_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_add_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_discord.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xhdpi/ic_website.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_add_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_discord.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxhdpi/ic_website.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxxhdpi/ic_discord.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBlocksTeam/openblocks-app/a6cd2564076acd3f22805c46ac7c8378bb4dbf7b/app/src/main/res/drawable-xxxhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_module_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_module_item_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_compiler.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_import.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 22 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_layout.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_project_manager.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_project_parser.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_run.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/_main_drawer_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 20 | 21 | 31 | 32 | 43 | 44 | 48 | 49 | 53 | 54 | 55 | 56 | 63 | 64 | 73 | 74 | 83 | 84 | 85 | 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_module_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_project_editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 23 | 24 | 25 | 26 | 36 | 37 | 41 | 42 | 46 | 47 | 51 | 52 | 53 | 54 | 58 | 59 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_edit_project_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 30 | 31 | 42 | 43 | 54 | 55 | 56 | 57 | 69 | 70 | 80 | 81 | 82 | 83 | 94 | 95 | 106 | 107 | 108 | 109 | 119 | 120 | 131 | 132 | 133 | 134 | 149 | 150 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_code_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 26 | 27 | 32 | 33 | 34 | 35 |