├── .gitignore
├── .gitmodules
├── .metadata
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-ldpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── main
│ │ ├── .gitignore
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── in
│ │ │ │ └── canews
│ │ │ │ └── pythonide
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── MyApplication.kt
│ │ └── res
│ │ │ ├── drawable-anydpi-v24
│ │ │ └── ic_bg_service_small.xml
│ │ │ ├── drawable-hdpi
│ │ │ └── ic_bg_service_small.png
│ │ │ ├── drawable-mdpi
│ │ │ └── ic_bg_service_small.png
│ │ │ ├── drawable-xhdpi
│ │ │ └── ic_bg_service_small.png
│ │ │ ├── drawable-xxhdpi
│ │ │ └── ic_bg_service_small.png
│ │ │ ├── drawable
│ │ │ ├── app_icon.png
│ │ │ ├── launch_background.xml
│ │ │ └── logo.png
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-ldpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ │ └── xml
│ │ │ └── file_provider_paths.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
├── settings_aar.gradle
└── version.properties
├── assets
├── developers
│ ├── canewsin.jpg
│ └── pramukesh.jpg
├── fileicons
│ ├── fileicons.json
│ ├── git.svg
│ ├── image.svg
│ ├── json.svg
│ ├── python.svg
│ └── svg.svg
├── fonts
│ └── lucidasansdemibold.ttf
├── icons
│ ├── facebook.png
│ ├── facebook_dark.png
│ ├── github.png
│ ├── github_dark.png
│ ├── twitter.png
│ └── twitter_dark.png
├── logo.png
└── templates
│ └── python
│ ├── fibonacci.py
│ ├── helloworld.py
│ ├── listcomprehensions.py
│ ├── looponlist.py
│ ├── simplearithmetic.py
│ └── templates.json
├── buildapk.bat
├── buildtools
├── compile.bat
└── utils.dart
├── lib
├── controllers
│ ├── process_controller.dart
│ ├── project_controller.dart
│ ├── project_file_controller.dart
│ ├── project_view_controller.dart
│ ├── purchases_controller.dart
│ ├── ui_controller.dart
│ └── var_controller.dart
├── imports.dart
├── imports_conflicts.dart
├── main.dart
├── models
│ ├── enums.dart
│ └── models.dart
├── others
│ ├── common.dart
│ ├── constants.dart
│ ├── donation_const.dart
│ ├── extensions.dart
│ ├── native.dart
│ ├── strings.dart
│ └── utils.dart
└── widgets
│ ├── about_page.dart
│ ├── common.dart
│ ├── home_page.dart
│ ├── loading_page.dart
│ ├── log_page.dart
│ ├── project_view_page.dart
│ └── settings_page.dart
└── pubspec.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | .vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 | libOld/
33 | .project
34 | .classpath
35 | *.prefs
36 | pubspec.lock
37 |
38 | # Web related
39 | lib/generated_plugin_registrant.dart
40 |
41 | # Exceptions to above rules.
42 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
43 |
44 | # Android related
45 | # **/android/**/gradle-wrapper.jar
46 | **/android/.gradle
47 | **/android/captures/
48 | # **/android/gradlew
49 | # **/android/gradlew.bat
50 | **/android/local.properties
51 | **/android/key.properties
52 | **/android/**/GeneratedPluginRegistrant.java
53 |
54 | # iOS/XCode related
55 | **/ios/**/*.mode1v3
56 | **/ios/**/*.mode2v3
57 | **/ios/**/*.moved-aside
58 | **/ios/**/*.pbxuser
59 | **/ios/**/*.perspectivev3
60 | **/ios/**/*sync/
61 | **/ios/**/.sconsign.dblite
62 | **/ios/**/.tags*
63 | **/ios/**/.vagrant/
64 | **/ios/**/DerivedData/
65 | **/ios/**/Icon?
66 | **/ios/**/Pods/
67 | **/ios/**/.symlinks/
68 | **/ios/**/profile
69 | **/ios/**/xcuserdata
70 | **/ios/.generated/
71 | **/ios/Flutter/App.framework
72 | **/ios/Flutter/Flutter.framework
73 | **/ios/Flutter/Generated.xcconfig
74 | **/ios/Flutter/app.flx
75 | **/ios/Flutter/app.zip
76 | **/ios/Flutter/flutter_assets/
77 | **/ios/ServiceDefinitions.json
78 | **/ios/Runner/GeneratedPluginRegistrant.*
79 |
80 | # Exceptions to above rules.
81 | !**/ios/**/default.mode1v3
82 | !**/ios/**/default.mode2v3
83 | !**/ios/**/default.pbxuser
84 | !**/ios/**/default.perspectivev3
85 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
86 |
87 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "android/arm_python"]
2 | path = android/arm_python
3 | url = https://github.com/canewsin/arm_python.git
4 | [submodule "android/arm64_python"]
5 | path = android/arm64_python
6 | url = https://github.com/canewsin/arm64_python.git
7 | [submodule "android/x86_64_python"]
8 | path = android/x86_64_python
9 | url = https://github.com/canewsin/x86_64_python.git
10 | [submodule "android/x86_python"]
11 | path = android/x86_python
12 | url = https://github.com/canewsin/x86_python.git
13 | [submodule "android/common_python"]
14 | path = android/common_python
15 | url = https://github.com/canewsin/common_python.git
16 | [submodule "android/nativelibs_python"]
17 | path = android/nativelibs_python
18 | url = https://github.com/canewsin/nativelibs_python.git
19 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python IDE Mobile - IDE for Python 3
2 |
3 |
4 | Python IDE Mobile is a simple yet fully featured Python3 IDE without needing PC Setup for your python 3 projects.
5 |
6 | [
](https://play.google.com/store/apps/details?id=in.canews.pythonide)
9 |
10 | ## Installation
11 |
12 | ### From Google PlayStore :
13 | #### Android (arm, arm64, x86)
14 | - minimum Android version supported 16 (JellyBean).
15 | - Google Play Store Link https://play.google.com/store/apps/details?id=in.canews.pythonide
16 |
17 | #### Compiling Source :
18 |
19 | You need Flutter Framework to compile this App from Source.
20 |
21 | #### Installing Flutter : https://flutter.dev/docs/get-started/install
22 |
23 | ```
24 | git clone https://github.com/canewsin/pythonide.git
25 | cd pythonide
26 | flutter packages get
27 | ```
28 |
29 | After that create a file named `key.properties` in `android` directory
30 | and fill the below details, which are in capital letters, with your details.
31 | ```
32 | storeFile=ANDROID_KEY_STORE_FILE_PATH
33 | storePassword=KEY_STORE_PASSWORD
34 | keyAlias=KEY_ALIAS
35 | keyPassword=KEY_PASSWORD
36 | ```
37 |
38 | in root folder
39 |
40 | to build apk
41 | ```
42 | flutter build apk --no-shrink
43 | ```
44 |
45 | to build appbundle
46 | ```
47 | flutter build appbundle --no-shrink
48 | ```
49 |
50 | to run the app in Android Device / Emulator
51 |
52 | ```
53 | flutter run
54 | ```
55 |
56 | ## Donate
57 | BTC(Preferred) :
58 |
59 | `1ZeroNetyV5mKY9JF1gsm82TuBXHpfdLX`
60 |
61 | ETH :
62 |
63 | `0xa81a32dcce8e5bcb9792daa19ae7f964699ee536`
64 |
65 | UPI(Indian Users) :
66 |
67 | `pramukesh@upi`
68 |
69 | Liberapay :
70 |
71 | `https://liberapay.com/canews.in/donate`
72 |
73 | ## Contribute
74 | If you want to support project's further development, you can contribute your time or money, If you want to contribute money you can send bitcoin or other supported crypto currencies to above addresses or buy in-app purchases, if want to contribute translations or code.
75 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | !gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | # /gradlew
5 | # /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | /build/
9 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def versionProperties = new Properties()
10 | def versionPropertiesFile = rootProject.file('version.properties')
11 | if (versionPropertiesFile.exists()) {
12 | versionPropertiesFile.withReader('UTF-8') { reader ->
13 | versionProperties.load(reader)
14 | }
15 | }
16 |
17 | def flutterRoot = localProperties.getProperty('flutter.sdk')
18 | if (flutterRoot == null) {
19 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
20 | }
21 |
22 | def flutterVersionCode = versionProperties.getProperty('flutter.versionCode')
23 | if (flutterVersionCode == null) {
24 | flutterVersionCode = '1'
25 | }
26 |
27 | def flutterVersionName = versionProperties.getProperty('flutter.versionName')
28 | if (flutterVersionName == null) {
29 | flutterVersionName = '1.0'
30 | }
31 | def keystoreProperties = new Properties()
32 | def keystorePropertiesFile = rootProject.file('key.properties')
33 | if (keystorePropertiesFile.exists()) {
34 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
35 | }
36 | apply plugin: 'com.android.application'
37 | apply plugin: 'kotlin-android'
38 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
39 | apply plugin: 'com.jeppeman.locallydynamic'
40 |
41 | android {
42 | compileSdkVersion 31
43 |
44 | sourceSets {
45 | main.java.srcDirs += 'src/main/kotlin'
46 | }
47 |
48 | lintOptions {
49 | disable 'InvalidPackage'
50 | }
51 |
52 | defaultConfig {
53 | applicationId "in.canews.pythonide"
54 | minSdkVersion 21
55 | targetSdkVersion 31
56 | versionCode flutterVersionCode.toInteger()
57 | versionName flutterVersionName
58 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
59 | }
60 |
61 | signingConfigs {
62 | release {
63 | keyAlias keystoreProperties['keyAlias']
64 | keyPassword keystoreProperties['keyPassword']
65 | storeFile file(keystoreProperties['storeFile'])
66 | storePassword keystoreProperties['storePassword']
67 | }
68 | }
69 |
70 | buildTypes {
71 |
72 | release {
73 | signingConfig signingConfigs.release
74 | minifyEnabled = false
75 | shrinkResources = false
76 | }
77 |
78 | debug {
79 | applicationIdSuffix '.debug'
80 | signingConfig signingConfigs.release
81 | locallyDynamic {
82 | enabled = true
83 | throttleDownloadBy = 1000
84 | }
85 | defaultConfig.ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86_64'
86 | }
87 | }
88 | dynamicFeatures = [
89 | ":nativelibs_python",
90 | ":arm64_python", ":arm_python", ":common_python", ":x86_python", ":x86_64_python"
91 | ]
92 |
93 |
94 | }
95 |
96 | flutter {
97 | source '../..'
98 | }
99 |
100 | dependencies {
101 | def billing_version = "3.0.0"
102 | implementation "com.android.billingclient:billing:4.0.0"
103 |
104 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
105 | implementation 'com.google.android.gms:play-services-base:17.6.0'
106 |
107 | //Duplicate Classes Issue https://stackoverflow.com/a/60492942
108 | implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
109 |
110 | debugImplementation 'com.jeppeman.locallydynamic:locallydynamic-debug:0.3'
111 | releaseImplementation 'com.jeppeman.locallydynamic:locallydynamic:0.3'
112 |
113 | testImplementation 'junit:junit:4.13.2'
114 | androidTestImplementation 'androidx.test:runner:1.3.0'
115 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
116 | }
117 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/.gitignore:
--------------------------------------------------------------------------------
1 | jniLibs/
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
42 |
43 |
48 |
49 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/in/canews/pythonide/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package `in`.canews.pythonide
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.app.PendingIntent
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.content.pm.PackageInfo
9 | import android.graphics.Bitmap
10 | import android.graphics.BitmapFactory
11 | import android.net.Uri
12 | import android.os.Build
13 | import android.os.Bundle
14 | import android.os.PowerManager
15 | import android.provider.Settings
16 | import android.util.Log
17 | import androidx.annotation.NonNull
18 | import androidx.core.app.ShareCompat
19 | import androidx.core.content.FileProvider
20 | import androidx.core.content.pm.ShortcutInfoCompat
21 | import androidx.core.content.pm.ShortcutManagerCompat
22 | import androidx.core.graphics.drawable.IconCompat
23 | import com.google.android.gms.common.ConnectionResult
24 | import com.google.android.gms.common.GoogleApiAvailability
25 | import com.google.android.play.core.splitinstall.SplitInstallManager
26 | import com.google.android.play.core.splitinstall.SplitInstallRequest
27 | import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
28 | import com.jeppeman.locallydynamic.LocallyDynamicSplitInstallManagerFactory
29 | import io.flutter.embedding.android.FlutterActivity
30 | import io.flutter.embedding.engine.FlutterEngine
31 | import io.flutter.plugin.common.EventChannel
32 | import io.flutter.plugin.common.EventChannel.StreamHandler
33 | import io.flutter.plugin.common.MethodChannel
34 | import io.flutter.plugins.GeneratedPluginRegistrant
35 | import java.io.File
36 | import java.io.IOException
37 | import java.io.InputStream
38 |
39 |
40 | const val BATTERY_OPTIMISATION_RESULT_CODE = 1001
41 | const val PICK_USERJSON_FILE = 1002
42 | const val SAVE_USERJSON_FILE = 1003
43 | const val PICK_ZIP_FILE = 1004
44 | const val TAG = "MainActivity"
45 | const val CHANNEL = "in.canews.pythonide"
46 | const val EVENT_CHANNEL = "in.canews.pythonide/installModules"
47 |
48 | class MainActivity : FlutterActivity() {
49 |
50 | private var archName = ""
51 | private var splitInstallManager: SplitInstallManager? = null
52 | private lateinit var result: MethodChannel.Result
53 | private var mSessionId = -1
54 | private var mLaunchShortcutUrl = ""
55 |
56 | override fun onCreate(savedInstanceState: Bundle?) {
57 | super.onCreate(savedInstanceState)
58 | }
59 |
60 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
61 | GeneratedPluginRegistrant.registerWith(flutterEngine)
62 | MethodChannel(flutterEngine?.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
63 | when (call.method) {
64 | "batteryOptimisations" -> getBatteryOptimizations(result)
65 | "copyAssetsToCache" -> result.success(copyAssetsToCache())
66 | "getAppInstallTime" -> getAppInstallTime(result)
67 | "getAppLastUpdateTime" -> getAppLastUpdateTime(result)
68 | "isBatteryOptimized" -> isBatteryOptimized(result)
69 | "isPlayStoreInstall" -> result.success(isPlayStoreInstall(this))
70 | "initSplitInstall" -> {
71 | if (splitInstallManager == null)
72 | splitInstallManager = LocallyDynamicSplitInstallManagerFactory.create(this)
73 | result.success(true)
74 | }
75 | "isModuleInstallSupported" -> result.success(isModuleInstallSupported())
76 | "isRequiredModulesInstalled" -> result.success(isRequiredModulesInstalled())
77 | "moveTaskToBack" -> {
78 | moveTaskToBack(true)
79 | result.success(true)
80 | }
81 | "nativeDir" -> result.success(applicationInfo.nativeLibraryDir)
82 | "nativePrint" -> {
83 | Log.e("Flutter>nativePrint()",call.arguments())
84 | }
85 | "openJsonFile" -> openJsonFile(result)
86 | "openZipFile" -> openZipFile(result)
87 | "readJsonFromUri" -> readJsonFromUri(call.arguments.toString(), result)
88 | "readZipFromUri" -> readZipFromUri(call.arguments.toString(), result)
89 | "uninstallModules" -> uninstallModules()
90 | }
91 | }
92 | EventChannel(flutterEngine.dartExecutor, EVENT_CHANNEL).setStreamHandler(
93 | object : StreamHandler {
94 | lateinit var events: EventChannel.EventSink
95 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
96 | getArchName()
97 | loadAndLaunchModule(archName, events)
98 | }
99 |
100 | override fun onCancel(arguments: Any?) {
101 | events.endOfStream()
102 | }
103 | }
104 | )
105 | }
106 |
107 | private fun getAppInstallTime(result: MethodChannel.Result) {
108 | val info = context.packageManager.getPackageInfo(context.packageName,0);
109 | val field = PackageInfo::class.java.getField("firstInstallTime")
110 | val timeStamp = field.getLong(info)
111 | result.success(timeStamp.toString())
112 | }
113 |
114 | private fun getAppLastUpdateTime(result: MethodChannel.Result) {
115 | val info = context.packageManager.getPackageInfo(context.packageName,0);
116 | val field = PackageInfo::class.java.getField("lastUpdateTime")
117 | val timeStamp = field.getLong(info)
118 | result.success(timeStamp.toString())
119 | }
120 |
121 | private fun isPlayStoreInstall(context: Context): Boolean {
122 | val validInstallers: List = listOf("com.android.vending", "com.google.android.feedback")
123 | val installer = context.packageManager.getInstallerPackageName(context.packageName)
124 |
125 | return installer != null && validInstallers.contains(installer)
126 | }
127 |
128 | private fun isBatteryOptimized(result: MethodChannel.Result) {
129 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
130 | val packageName = packageName
131 | val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
132 | result.success(pm.isIgnoringBatteryOptimizations(packageName))
133 | } else {
134 | result.success(false)
135 | }
136 |
137 | }
138 |
139 | @SuppressLint("BatteryLife")
140 | private fun getBatteryOptimizations(resultT: MethodChannel.Result) {
141 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
142 | val intent = Intent()
143 | val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
144 | if (!pm.isIgnoringBatteryOptimizations(packageName)) {
145 | intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
146 | intent.data = Uri.parse("package:$packageName")
147 | startActivityForResult(intent, BATTERY_OPTIMISATION_RESULT_CODE)
148 | result = resultT
149 | } else {
150 | resultT.success(true)
151 | }
152 | }
153 | }
154 |
155 | override fun onActivityResult(
156 | requestCode: Int,
157 | resultCode: Int,
158 | data: Intent?
159 | ) {
160 | if (requestCode == BATTERY_OPTIMISATION_RESULT_CODE) {
161 | if (resultCode == Activity.RESULT_OK) {
162 | resultSuccess(true)
163 | } else {
164 | resultSuccess(false)
165 | }
166 | } else if (requestCode == SAVE_USERJSON_FILE) {
167 | if (resultCode == Activity.RESULT_OK) {
168 | resultSuccess("successfully saved users.json file")
169 | } else {
170 | resultSuccess("failed to save file")
171 | }
172 | } else if (requestCode == PICK_USERJSON_FILE) {
173 | if (resultCode == Activity.RESULT_OK) {
174 | if (data?.data != null) {
175 | resultSuccess(data.data.toString())
176 | }
177 | } else {
178 | result.error("526", "Error Picking User Json File", "Error Picking User Json File")
179 | }
180 | } else if (requestCode == PICK_ZIP_FILE) {
181 | if (resultCode == Activity.RESULT_OK) {
182 | if (data?.data != null) {
183 | resultSuccess(data.data.toString())
184 | }
185 | } else {
186 | result.error("527", "Error Picking Plugin File", "Error Picking Plugin File")
187 | }
188 | }
189 | super.onActivityResult(requestCode, resultCode, data)
190 | }
191 |
192 | private fun resultSuccess(msg : Any) {
193 | result.runCatching {
194 | success(msg)
195 | }.onFailure {
196 | if (it is IllegalStateException) {
197 | Log.e("MainActivity>resultSuc>", it.message!!)
198 | }
199 | }
200 | }
201 |
202 | private fun openZipFile(
203 | resultT: MethodChannel.Result
204 | ) =
205 | openFileIntent(
206 | resultT,
207 | Intent.ACTION_OPEN_DOCUMENT,
208 | "application/zip",
209 | PICK_ZIP_FILE
210 | )
211 |
212 | private fun openJsonFile(
213 | resultT: MethodChannel.Result
214 | ) =
215 | openFileIntent(
216 | resultT,
217 | Intent.ACTION_OPEN_DOCUMENT,
218 | "application/json",
219 | PICK_USERJSON_FILE
220 | )
221 |
222 | private fun openFileIntent(
223 | resultT: MethodChannel.Result,
224 | intentAction: String,
225 | intentType: String,
226 | intentCode: Int
227 | ) {
228 | val intent = Intent(intentAction).apply {
229 | addCategory(Intent.CATEGORY_OPENABLE)
230 | type = intentType
231 | result = resultT
232 | }
233 | startActivityForResult(intent, intentCode)
234 | }
235 |
236 | private fun readJsonFromUri(path: String, resultT: MethodChannel.Result) = copyFileToTempPath(path, resultT, "/users.json")
237 |
238 | private fun readZipFromUri(path: String, resultT: MethodChannel.Result) = copyFileToTempPath(path, resultT, "/plugin.zip")
239 |
240 | @Throws(IOException::class)
241 | private fun copyFileToTempPath(
242 | path: String?,
243 | resultT: MethodChannel.Result? = null,
244 | filename: String,
245 | inputStreamA: InputStream? = null
246 | ) {
247 | var inputstream: InputStream? = null
248 | if (inputStreamA != null) {
249 | inputstream = inputStreamA
250 | } else {
251 | if (path != null)
252 | if (path.startsWith("content://")) {
253 | inputstream = contentResolver.openInputStream(Uri.parse(path))
254 | } else if (path.startsWith("/")) {
255 | inputstream = File(path).inputStream()
256 | }
257 | }
258 | inputstream.use { inputStream ->
259 | val tempFilePath = cacheDir.path + "/" + filename
260 | val tempFile = File(tempFilePath)
261 | if (tempFile.exists()) tempFile.delete()
262 | tempFile.createNewFile()
263 | inputStream?.toFile(tempFilePath)
264 | resultT?.success(File(tempFilePath).absoluteFile.absolutePath)
265 | tempFile.deleteOnExit()
266 | }
267 | }
268 |
269 | private fun InputStream.toFile(path: String) {
270 | use { input ->
271 | File(path).outputStream().use { input.copyTo(it) }
272 | }
273 | }
274 |
275 | private fun copyAssetsToCache(): Boolean {
276 | getArchName()
277 | try {
278 | if (splitInstallManager?.installedModules!!.contains("common_python")) {
279 | getAssetFiles("site_packages_common.zip")
280 | }
281 | if (splitInstallManager?.installedModules!!.contains(archName + "_python")) {
282 | getAssetFiles("python38_$archName.zip")
283 | }
284 | } catch (e: IOException) {
285 | return false
286 | }
287 | return true
288 | }
289 |
290 | private fun getAssetFiles(fileName: String) {
291 | val assetManager = createPackageContext(packageName, 0).assets
292 | val assistContent = assetManager.open(fileName)
293 | copyFileToTempPath(inputStreamA = assistContent, filename = fileName, path = null)
294 | }
295 |
296 | private fun isModuleInstallSupported(): Boolean =
297 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
298 | && isGooglePlayServicesAvailable(this)
299 |
300 | private fun isGooglePlayServicesAvailable(activity: Activity?): Boolean {
301 | val googleApiAvailability: GoogleApiAvailability = GoogleApiAvailability.getInstance()
302 | val status: Int = googleApiAvailability.isGooglePlayServicesAvailable(activity)
303 | if (status != ConnectionResult.SUCCESS) {
304 | // if (googleApiAvailability.isUserResolvableError(status)) {
305 | // googleApiAvailability.getErrorDialog(activity, status, 2404).show()
306 | // }
307 | return false
308 | }
309 | return true
310 | }
311 |
312 | private fun getArchName() {
313 | val arch = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
314 | Build.SUPPORTED_ABIS
315 | } else {
316 | TODO("VERSION.SDK_INT < LOLLIPOP")
317 | }
318 | if (archName.isEmpty())
319 | archName = if (arch.contains("arm64-v8a")) {
320 | "arm64"
321 | } else if (arch.contains("armeabi-v7a")) {
322 | "arm"
323 | } else if (arch.contains("x86_64")) {
324 | "x86_64"
325 | } else {
326 | "x86"
327 | }
328 | }
329 |
330 | /**
331 | * Load a feature by module name.
332 | * @param name The name of the feature module to load.
333 | */
334 | private fun loadAndLaunchModule(name: String, eventSink: EventChannel.EventSink?) {
335 | if (isModuleInstalled(name) == true)
336 | return
337 | val request = SplitInstallRequest.newBuilder()
338 | .addModule(name + "_python")
339 | .addModule("common_python")
340 | .build()
341 | splitInstallManager?.startInstall(request)?.addOnSuccessListener { sessionId ->
342 | mSessionId = sessionId
343 | }
344 | splitInstallManager?.registerListener { state ->
345 | if (state.sessionId() == mSessionId) {
346 | when (state.status()) {
347 | SplitInstallSessionStatus.DOWNLOADING -> {
348 | val msg = """
349 | {
350 | "status" : ${state.status()},
351 | "downloaded" : ${state.bytesDownloaded()},
352 | "total" : ${state.totalBytesToDownload()}
353 | }
354 | """.trimIndent()
355 | eventSink?.success(msg)
356 | }
357 | SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
358 | startIntentSender(state.resolutionIntent()?.intentSender, null, 0, 0, 0)
359 | }
360 | else -> {
361 | val msg = """
362 | {
363 | "status" : ${state.status()}
364 | }
365 | """.trimIndent()
366 | eventSink?.success(msg)
367 | }
368 | }
369 | }
370 | }
371 | }
372 |
373 | private fun isModuleInstalled(name: String): Boolean? =
374 | splitInstallManager?.installedModules?.contains(name)
375 |
376 | private fun isRequiredModulesInstalled(): Boolean = isModuleInstalled("common_python") == true &&
377 | isModuleInstalled(archName + "_python") == true
378 |
379 | private fun uninstallModules() {
380 | val installedModules = splitInstallManager?.installedModules?.toList()
381 | splitInstallManager?.deferredUninstall(listOf("common_python", archName + "_python"))?.addOnSuccessListener {
382 | Log.d("SplitModuleUninstall:>:","Uninstalling $installedModules")
383 | }
384 | context.cacheDir.deleteRecursively()
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/in/canews/pythonide/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package `in`.canews.pythonide
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import com.google.android.play.core.splitcompat.SplitCompat
6 | import com.google.android.play.core.splitcompat.SplitCompatApplication
7 | import io.flutter.FlutterInjector
8 |
9 |
10 | internal class MyApplication : SplitCompatApplication() {
11 |
12 | override fun onCreate() {
13 | super.onCreate()
14 | FlutterInjector.instance().flutterLoader().startInitialization(this)
15 | }
16 |
17 | private var mCurrentActivity: Activity? = null
18 |
19 | fun getCurrentActivity(): Activity? {
20 | return mCurrentActivity
21 | }
22 |
23 | fun setCurrentActivity(mCurrentActivity: Activity?) {
24 | this.mCurrentActivity = mCurrentActivity
25 | }
26 |
27 | override fun attachBaseContext(base: Context?) {
28 | super.attachBaseContext(base)
29 | SplitCompat.install(base!!)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-anydpi-v24/ic_bg_service_small.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
7 |
9 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_bg_service_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable-hdpi/ic_bg_service_small.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_bg_service_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable-mdpi/ic_bg_service_small.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_bg_service_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable-xhdpi/ic_bg_service_small.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_bg_service_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable-xxhdpi/ic_bg_service_small.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | -
13 |
14 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/drawable/logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Arm64 Binaries
3 | Arm Binaries
4 | Common Binaries
5 | Native Binaries
6 | x86 Binaries
7 | x86_64 Binaries
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.5.0'
3 | ext.gradle_version = '4.2.0'
4 | repositories {
5 | google()
6 | mavenCentral()
7 | maven {
8 | url "https://plugins.gradle.org/m2"
9 | }
10 | }
11 |
12 | dependencies {
13 | classpath "com.android.tools.build:gradle:${gradle_version}"
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
15 | classpath "com.jeppeman.locallydynamic.gradle:plugin:0.2"
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | mavenCentral()
23 | }
24 | }
25 |
26 | rootProject.buildDir = '../build'
27 | subprojects {
28 | project.buildDir = "${rootProject.buildDir}/${project.name}"
29 | }
30 | subprojects {
31 | project.evaluationDependsOn(':app')
32 | }
33 |
34 | task clean(type: Delete) {
35 | delete rootProject.buildDir
36 | }
37 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4096M
2 | # TODO : Once Binaries are compiled with new NDK than NDK 21 RC1
3 | # we can remove below uncompressed property
4 | # https://stackoverflow.com/a/55969995
5 | android.bundle.enableUncompressedNativeLibs=false
6 | android.experimental.enableNewResourceShrinker=true
7 | android.enableR8=true
8 | android.useAndroidX=true
9 | android.enableJetifier=true
10 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':common_python'
3 | include ':nativelibs_python'
4 | include ':arm64_python'
5 | include ':arm_python'
6 | include ':x86_python'
7 | include ':x86_64_python'
8 |
9 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
10 |
11 | def plugins = new Properties()
12 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
13 | if (pluginsFile.exists()) {
14 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
15 | }
16 |
17 | plugins.each { name, path ->
18 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
19 | include ":$name"
20 | project(":$name").projectDir = pluginDirectory
21 | }
22 |
--------------------------------------------------------------------------------
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/android/version.properties:
--------------------------------------------------------------------------------
1 | flutter.versionName=v 0.1.7
2 | flutter.versionCode=17
3 |
--------------------------------------------------------------------------------
/assets/developers/canewsin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/developers/canewsin.jpg
--------------------------------------------------------------------------------
/assets/developers/pramukesh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/developers/pramukesh.jpg
--------------------------------------------------------------------------------
/assets/fileicons/fileicons.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "fileName": "git.svg",
4 | "supportedExt": [
5 | "/.git"
6 | ]
7 | },
8 | "image": {
9 | "fileName": "image.svg",
10 | "supportedExt": [
11 | "jpeg",
12 | "png"
13 | ]
14 | },
15 | "json": {
16 | "fileName": "json.svg",
17 | "supportedExt": [
18 | "json",
19 | "props"
20 | ]
21 | },
22 | "python": {
23 | "fileName": "python.svg",
24 | "supportedExt": [
25 | "py"
26 | ]
27 | },
28 | "svg": {
29 | "fileName": "svg.svg",
30 | "supportedExt": [
31 | "svg"
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/assets/fileicons/git.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/assets/fileicons/image.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/fileicons/json.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/fileicons/python.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/assets/fileicons/svg.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/fonts/lucidasansdemibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/fonts/lucidasansdemibold.ttf
--------------------------------------------------------------------------------
/assets/icons/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/facebook.png
--------------------------------------------------------------------------------
/assets/icons/facebook_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/facebook_dark.png
--------------------------------------------------------------------------------
/assets/icons/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/github.png
--------------------------------------------------------------------------------
/assets/icons/github_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/github_dark.png
--------------------------------------------------------------------------------
/assets/icons/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/twitter.png
--------------------------------------------------------------------------------
/assets/icons/twitter_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/icons/twitter_dark.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canewsin/python_ide/3e7482fbe113ab6ee592a1768448a3b7e3947404/assets/logo.png
--------------------------------------------------------------------------------
/assets/templates/python/fibonacci.py:
--------------------------------------------------------------------------------
1 | # Python 3: Fibonacci series up to n
2 | def fib(n):
3 | a, b = 0, 1
4 | while a < n:
5 | print(a, end=' ')
6 | a, b = b, a+b
7 | print()
8 | print(fib(1000))
--------------------------------------------------------------------------------
/assets/templates/python/helloworld.py:
--------------------------------------------------------------------------------
1 | print('Hello World!')
2 | #Output
3 | #Hello World!
--------------------------------------------------------------------------------
/assets/templates/python/listcomprehensions.py:
--------------------------------------------------------------------------------
1 | # Python 3: List comprehensions
2 | fruits = ['Banana', 'Apple', 'Lime']
3 | loud_fruits = [fruit.upper() for fruit in fruits]
4 | print(loud_fruits)
5 | #Output
6 | # ['BANANA', 'APPLE', 'LIME']
7 |
8 | # List and the enumerate function
9 | print(list(enumerate(fruits)))
10 | #Output
11 | # [(0, 'Banana'), (1, 'Apple'), (2, 'Lime')]
--------------------------------------------------------------------------------
/assets/templates/python/looponlist.py:
--------------------------------------------------------------------------------
1 | # For loop on a list
2 | numbers = [2, 4, 6, 8]
3 | product = 1
4 | for number in numbers:
5 | product = product * number
6 |
7 | print('The product is:', product)
8 |
9 | #Output
10 | #The product is: 384
--------------------------------------------------------------------------------
/assets/templates/python/simplearithmetic.py:
--------------------------------------------------------------------------------
1 | # Python 3: Simple arithmetic
2 | print(1 / 2)
3 | #Output
4 | # 0.5
5 | print(2 ** 3)
6 | #Output
7 | # 8
8 | print(17 / 3) # classic division returns a float
9 | #Output
10 | # 5.666666666666667
11 | print(17 // 3) # floor division
12 | #Output
13 | # 5
--------------------------------------------------------------------------------
/assets/templates/python/templates.json:
--------------------------------------------------------------------------------
1 | {
2 | "HelloWorld": {
3 | "fileName": "helloworld.py",
4 | "source": "Python WebSite HomePage"
5 | },
6 | "Fibonacci": {
7 | "fileName": "fibonacci.py",
8 | "source": "Python WebSite HomePage"
9 | },
10 | "List Comprehensions": {
11 | "fileName": "listcomprehensions.py",
12 | "source": "Python WebSite HomePage"
13 | },
14 | "Loop On List": {
15 | "fileName": "looponlist.py",
16 | "source": "Python WebSite HomePage"
17 | },
18 | "Simple Arithematic": {
19 | "fileName": "simplearithmetic.py",
20 | "source": "Python WebSite HomePage"
21 | }
22 | }
--------------------------------------------------------------------------------
/buildapk.bat:
--------------------------------------------------------------------------------
1 | dart buildtools\utils.dart compile
--------------------------------------------------------------------------------
/buildtools/compile.bat:
--------------------------------------------------------------------------------
1 | flutter build apk
--------------------------------------------------------------------------------
/buildtools/utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | main(List args) {
5 | String arg = (args.isEmpty) ? '' : args[0];
6 | switch (arg) {
7 | case 'modules':
8 | modules();
9 | break;
10 | case 'nonpy':
11 | removeNonPy();
12 | break;
13 | case 'compile':
14 | compile();
15 | break;
16 | case 'update':
17 | updateZeroNetCode();
18 | break;
19 | default:
20 | compile();
21 | }
22 | }
23 |
24 | updateZeroNetCode() {
25 | var zeronetPath = Directory.current.path + '/build/ZeroNet-py3';
26 | if (Directory(zeronetPath).existsSync()) {
27 | print('Deleting ZeroNet Git Repo');
28 | Directory(zeronetPath).deleteSync(recursive: true);
29 | }
30 | var process = Process.runSync('git', [
31 | 'clone',
32 | 'https://github.com/HelloZeroNet/ZeroNet.git',
33 | '--depth=1',
34 | zeronetPath,
35 | ]);
36 | if (process.exitCode == 0) {
37 | print('Successfully downloaded Zeronet Repo');
38 |
39 | print('Deleting git history');
40 | Directory(zeronetPath + '/.git').deleteSync(recursive: true);
41 |
42 | Process.runSync('cd', ['build']);
43 | Process.runSync('7z', ['a', '-tzip', 'zeronet_py3.zip', 'ZeroNet']);
44 | }
45 | }
46 |
47 | compile() {
48 | String versionProp = '';
49 | var content = File('android/version.properties').readAsStringSync();
50 | versionProp =
51 | content.split('\n')[0].replaceAll('flutter.versionName=', '').trim();
52 | var result = Process.runSync('git', [
53 | 'log',
54 | '-1',
55 | '--pretty=%B',
56 | ]);
57 | var ver = (result.stdout as String).split('\n')[0].trim();
58 | if (ver.contains(versionProp)) {
59 | print('Repo is Clear to Compile APK For Release...');
60 | print('Compiling APK For Release...');
61 | Process.start('buildtools\\compile.bat', []).then((Process result) {
62 | result.stdout.listen((onData) {
63 | // print('Output : ');
64 | print(utf8.decode(onData));
65 | });
66 | result.stderr.listen((onData) {
67 | // print('Error : ');
68 | print(utf8.decode(onData));
69 | });
70 | });
71 | } else
72 | throw "Update version.properties";
73 | }
74 |
75 | var totalFilesList = [];
76 | var nonPyFiles = [];
77 | var pyFiles = [];
78 | var pyLibDir = '\\lib\\python3.8';
79 | removeNonPy() {
80 | Directory dir = Directory.current;
81 | //Here
82 | recursiveHelper(Directory(dir.path + pyLibDir));
83 | String nonPy = '';
84 | nonPyFiles.forEach((f) => nonPy = nonPy + f + '\n');
85 |
86 | File f = File('files-nonpy');
87 | if (f.existsSync()) f.deleteSync();
88 | f.createSync();
89 | f.writeAsStringSync(nonPy);
90 |
91 | print(totalFilesList.length);
92 | print(pyFiles.length);
93 | print(nonPyFiles.length);
94 | }
95 |
96 | List ls = [
97 | 'config-3.8',
98 | 'test',
99 | 'tests',
100 | 'ensurepip',
101 | 'idle_test',
102 | ];
103 |
104 | recursiveHelper(Directory dir) {
105 | for (var file in dir.listSync()) {
106 | if (file is File) {
107 | var filePath = file.path.replaceAll(dir.path + '\\', '');
108 | totalFilesList.add(filePath);
109 | if (filePath.endsWith('.py') || filePath.endsWith('.so')) {
110 | print('Python file $filePath');
111 | pyFiles.add(filePath);
112 | } else {
113 | print('Non Python file $filePath');
114 | if (filePath.endsWith('.exe') ||
115 | filePath.endsWith('.bat') ||
116 | filePath.endsWith('.ps1')) {
117 | print("Deleting File at:" + filePath);
118 | nonPyFiles.add(filePath);
119 | file.deleteSync(recursive: true);
120 | } else {
121 | print("Orphan File at " + filePath);
122 | }
123 | }
124 | } else {
125 | for (var item in ls) {
126 | if (file.path.endsWith('\\$item')) {
127 | file.deleteSync(recursive: true);
128 | } else {
129 | recursiveHelper(file);
130 | }
131 | }
132 | }
133 | }
134 | }
135 |
136 | modules() {
137 | Directory dir = Directory.current;
138 | File module = File('modules');
139 | String modules = module.readAsStringSync();
140 | List validModules = [];
141 | modules.split('\n').forEach((f) {
142 | if (f.contains('usr/')) {
143 | var i = f.indexOf('usr/');
144 | validModules.add(f.substring(i + 4).replaceAll('/', '\\'));
145 | }
146 | });
147 | var totalFilesList = [];
148 | var deletedFilesList = [];
149 | var pyLibDir = '\\lib\\python3.8';
150 | for (var file in Directory(dir.path + pyLibDir).listSync(recursive: true)) {
151 | if (file is File) {
152 | var filePath = file.path.replaceAll(dir.path + '\\', '');
153 | totalFilesList.add(filePath);
154 | if (validModules.indexOf(filePath) == -1) {
155 | print('deleting ${file.path}');
156 | deletedFilesList.add(filePath);
157 | file.deleteSync(recursive: false);
158 | }
159 | }
160 | }
161 | String deleted = '';
162 | deletedFilesList.forEach((f) => deleted = deleted + f + '\n');
163 |
164 | File f = File('modules-deleted');
165 | if (f.existsSync()) f.deleteSync();
166 | f.createSync();
167 | f.writeAsStringSync(deleted);
168 |
169 | deleteEmptyDirs(dir, pyLibDir);
170 | deleteEmptyDirs(dir, pyLibDir);
171 | print(totalFilesList.length);
172 | // print(totalFilesList[0]);
173 | print(validModules.length);
174 | // print(validModules[0]);
175 | print(deletedFilesList.length);
176 | // print(deletedFilesList[0]);
177 | }
178 |
179 | deleteEmptyDirs(Directory dir, String pyLibDir) {
180 | print('Deleting Empty Dirs');
181 | for (var file in Directory(dir.path + pyLibDir).listSync(recursive: true)) {
182 | if (file is Directory) {
183 | if (file.listSync(recursive: true).length == 0) {
184 | file.deleteSync(recursive: true);
185 | }
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/lib/controllers/process_controller.dart:
--------------------------------------------------------------------------------
1 | import '../imports.dart';
2 | import '../imports_conflicts.dart';
3 |
4 | class ProcessController extends GetxController {
5 | var status = ProcessStatus.Idle.obs;
6 | var consoleLog = ''.obs;
7 |
8 | void appendConsoleLog(Object log) => consoleLog.value += log.toString();
9 |
10 | void clearConsoleLog() => consoleLog.value = '';
11 |
12 | void changeProcessStatus(ProcessStatus statusInput) =>
13 | status.value = statusInput;
14 |
15 | void runProcess(String path) {
16 | var python = appNativeDir + '/libpython3.8.so';
17 | if (File(python).existsSync()) {
18 | Process.start('$python', [
19 | path,
20 | ], environment: {
21 | "LD_LIBRARY_PATH": "$libDir:$libDir64:/system/lib64",
22 | 'PYTHONHOME': '$dataDir/usr',
23 | 'PYTHONPATH': '$python',
24 | })
25 | .then((proc) {
26 | proc.stderr.listen((onData) {
27 | appendConsoleLog(utf8.decode(onData));
28 | });
29 | proc.stdout.listen((onData) {
30 | appendConsoleLog(utf8.decode(onData));
31 | });
32 | })
33 | .whenComplete(
34 | () => processController.changeProcessStatus(ProcessStatus.Idle),
35 | )
36 | .catchError((e) {
37 | if (e is ProcessException) {
38 | printOut(e.toString());
39 | }
40 | processController.changeProcessStatus(ProcessStatus.Error);
41 | appendConsoleLog({'ZeroNetStatus': 'ERROR'});
42 | appendConsoleLog({'console': e.toString()});
43 | });
44 | } else {
45 | //TODO: Improve Error Trace here
46 | appendConsoleLog({'console': 'Python Binary Not Found'});
47 | processController.changeProcessStatus(ProcessStatus.Error);
48 | var contents = Directory(appNativeDir).listSync(recursive: true);
49 | for (var item in contents) {
50 | appendConsoleLog({'console': item.name()});
51 | appendConsoleLog({'console': item.path});
52 | }
53 | }
54 | return;
55 | }
56 | }
57 |
58 | final processController = Get.put(ProcessController());
59 |
--------------------------------------------------------------------------------
/lib/controllers/project_controller.dart:
--------------------------------------------------------------------------------
1 | import '../imports.dart';
2 | import '../imports_conflicts.dart';
3 |
4 | class ProjectController extends GetxController {
5 | var projects = [Project(id: null)].obs;
6 | List templates = [];
7 | List fileIcons = [];
8 | var currentTemplate = 'helloworld.py'.obs;
9 |
10 | var createdNewProjectName = ''.obs;
11 | var createdNewProjectPath = ''.obs;
12 |
13 | void loadCodeTemplates() async {
14 | var templatesStr = await rootBundle.loadString(
15 | 'assets/templates/python/templates.json',
16 | );
17 | var templateMap = json.decode(templatesStr);
18 |
19 | for (var item in (templateMap as Map).keys) {
20 | var codeTemplate = CodeTemplate().fromMap(item, templateMap[item]);
21 | templates.add(codeTemplate);
22 | }
23 | }
24 |
25 | void loadFileIcons() async {
26 | var templatesStr = await rootBundle.loadString(
27 | 'assets/fileicons/fileicons.json',
28 | );
29 | var iconsMap = json.decode(templatesStr);
30 |
31 | for (var item in (iconsMap as Map).keys) {
32 | var codeTemplate = FileIcon().fromMap(item, iconsMap[item]);
33 | fileIcons.add(codeTemplate);
34 | }
35 | }
36 |
37 | void loadExistingProjects() {
38 | Directory projectsDirectory = Directory(appPrivDocsDir.path + '/Projects');
39 | if (!projectsDirectory.existsSync()) {
40 | return;
41 | }
42 | File projectsFile = File(projectsDirectory.path + '/Projects.json');
43 | if (projectsFile.existsSync()) {
44 | var projectsList = json.decode(projectsFile.readAsStringSync());
45 | for (Map project in projectsList) {
46 | var projectLoaded = loadProjectFromPath(project[project.keys.first]);
47 | addProjectToList(projectLoaded);
48 | }
49 | }
50 | }
51 |
52 | Project loadProjectFromPath(String path) {
53 | File propsFile = File(path + '/project.props');
54 | Project project;
55 | if (propsFile.existsSync()) {
56 | project = Project().fromMap(json.decode(propsFile.readAsStringSync()));
57 | }
58 | return project;
59 | }
60 |
61 | void addProjectToList(Project project) {
62 | if (projects.where((p) => p.id == project.id).isEmpty)
63 | projects.add(project);
64 | }
65 |
66 | void addProject(Project project) {
67 | addProjectToList(project);
68 | saveToExistingProjects(project);
69 | }
70 |
71 | void removeProject(String id) {
72 | loadExistingProjects();
73 | removeProjectFromList(id);
74 | }
75 |
76 | void removeProjectFromList(String id) {
77 | var projectsLoaded = projects.where((p) => p.id == id);
78 | if (projectsLoaded.isNotEmpty) {
79 | var project = projectsLoaded.first;
80 | removeProjectFromFileSystem(project.id);
81 | projects.remove(project);
82 | }
83 | }
84 |
85 | void removeProjectFromFileSystem(String id) {
86 | Directory projectsDirectory = Directory(appPrivDocsDir.path + '/Projects');
87 | File projectsFile = File(projectsDirectory.path + '/Projects.json');
88 | List