├── .gitignore ├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml ├── code.iml ├── libraries │ └── Dart_SDK.xml └── codeStyles │ └── Project.xml ├── org.purplegraphite.code ├── lib │ ├── src │ │ ├── common │ │ │ ├── assets.dart │ │ │ ├── constants.dart │ │ │ ├── ui.dart │ │ │ ├── routing_const.dart │ │ │ ├── strings.dart │ │ │ └── routes.dart │ │ ├── models │ │ │ ├── hive │ │ │ │ ├── latest.dart │ │ │ │ ├── settings │ │ │ │ │ ├── editorSettings.dart │ │ │ │ │ ├── consoleSettings.dart │ │ │ │ │ ├── generalSettings.dart │ │ │ │ │ ├── generalSettings.g.dart │ │ │ │ │ ├── themeSettings.g.dart │ │ │ │ │ └── themeSettings.dart │ │ │ │ ├── history.dart │ │ │ │ ├── repository.dart │ │ │ │ └── history.g.dart │ │ │ ├── plain_model │ │ │ │ ├── ThemeColors.dart │ │ │ │ └── entity.dart │ │ │ ├── view_model │ │ │ │ ├── browser_controller.dart │ │ │ │ └── terminal_controller.dart │ │ │ └── provider │ │ │ │ └── history.dart │ │ ├── utils │ │ │ ├── string.dart │ │ │ ├── logman.dart │ │ │ ├── theme.dart │ │ │ ├── checksum.dart │ │ │ ├── fileutils.dart │ │ │ └── permissions.dart │ │ └── ui │ │ │ ├── screens │ │ │ ├── start │ │ │ │ └── controller.dart │ │ │ ├── history.dart │ │ │ ├── terminal.dart │ │ │ └── editor_tab.dart │ │ │ └── components │ │ │ ├── app_title.dart │ │ │ ├── about │ │ │ ├── version.dart │ │ │ └── about.dart │ │ │ ├── popup_menu_tile.dart │ │ │ ├── acrylic.dart │ │ │ ├── buttons │ │ │ └── action_tabs_button.dart │ │ │ ├── ask_dialog.dart │ │ │ ├── newfolder_dialog.dart │ │ │ ├── drawer │ │ │ └── editor_drawer.dart │ │ │ └── start_tips │ │ │ └── tips.dart │ ├── service │ │ └── saver.dart │ └── main.dart ├── android │ ├── settings_aar.gradle │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── org │ │ │ │ │ │ └── purplegraphite │ │ │ │ │ │ └── code │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── analysis_options.yaml ├── assets │ └── fonts │ │ └── Source_Code_Pro │ │ ├── SourceCodePro-Bold.ttf │ │ ├── SourceCodePro-Black.ttf │ │ ├── SourceCodePro-Italic.ttf │ │ ├── SourceCodePro-Light.ttf │ │ ├── SourceCodePro-Medium.ttf │ │ ├── SourceCodePro-Regular.ttf │ │ ├── SourceCodePro-SemiBold.ttf │ │ ├── SourceCodePro-BlackItalic.ttf │ │ ├── SourceCodePro-BoldItalic.ttf │ │ ├── SourceCodePro-ExtraLight.ttf │ │ ├── SourceCodePro-LightItalic.ttf │ │ ├── SourceCodePro-MediumItalic.ttf │ │ ├── SourceCodePro-SemiBoldItalic.ttf │ │ ├── SourceCodePro-ExtraLightItalic.ttf │ │ └── OFL.txt ├── .metadata ├── .gitignore ├── test │ └── widget_test.dart └── pubspec.yaml ├── packages └── creamy_field │ ├── screenshots │ ├── flutter_01.png │ ├── flutter_02.png │ └── flutter_03.png │ ├── analysis_options.yaml │ ├── .metadata │ ├── example │ ├── .metadata │ ├── test │ │ └── widget_test.dart │ ├── README.md │ ├── pubspec.yaml │ ├── analysis_options.yaml │ ├── .gitignore │ ├── lib │ │ └── main.dart │ └── pubspec.lock │ ├── pubspec.yaml │ ├── lib │ ├── src │ │ ├── syntax_highlighter.dart │ │ ├── syntax_highlighter │ │ │ ├── dummy_syntax_highlighter.dart │ │ │ ├── language_type.dart │ │ │ └── creamy_syntax_highlighter.dart │ │ ├── text_tools │ │ │ ├── extensions.dart │ │ │ └── toolbar_options.dart │ │ └── decoration │ │ │ └── horizontal_scrollable.dart │ └── creamy_field.dart │ ├── LICENSE │ ├── CHANGELOG.md │ ├── .gitignore │ ├── README.md │ ├── pubspec.lock │ └── test │ └── creamy_field_test.dart ├── CHANGELOG.md ├── tools ├── tag_version.sh ├── build_release.sh ├── status.sh ├── support │ └── snr.py └── update_version.sh ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── master.yml │ ├── release.yml │ ├── main_package.yml │ └── main.yml ├── LICENSE.md ├── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | secrets -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/assets.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/latest.dart: -------------------------------------------------------------------------------- 1 | // Unimplemented 2 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/editorSettings.dart: -------------------------------------------------------------------------------- 1 | // Unimplemented 2 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/consoleSettings.dart: -------------------------------------------------------------------------------- 1 | // Unimplemented 2 | -------------------------------------------------------------------------------- /packages/creamy_field/screenshots/flutter_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/packages/creamy_field/screenshots/flutter_01.png -------------------------------------------------------------------------------- /packages/creamy_field/screenshots/flutter_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/packages/creamy_field/screenshots/flutter_02.png -------------------------------------------------------------------------------- /packages/creamy_field/screenshots/flutter_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/packages/creamy_field/screenshots/flutter_03.png -------------------------------------------------------------------------------- /org.purplegraphite.code/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | lines_longer_than_80_chars: ignore -------------------------------------------------------------------------------- /org.purplegraphite.code/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/string.dart: -------------------------------------------------------------------------------- 1 | class StringUtils { 2 | static String toFirstLetterUppercase(String word) { 3 | return '${word[0].toUpperCase()}${word.substring(1)}'; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Black.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Italic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Light.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/constants.dart: -------------------------------------------------------------------------------- 1 | /// The duration of the horizontal scroll animation that occurs when a tab is tapped. 2 | const Duration kTabScrollDuration = Duration(milliseconds: 300); 3 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-SemiBold.ttf -------------------------------------------------------------------------------- /packages/creamy_field/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-BlackItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-BoldItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-LightItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-MediumItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predatorx7/snake_code/HEAD/org.purplegraphite.code/assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/logman.dart: -------------------------------------------------------------------------------- 1 | import 'package:logging/logging.dart'; 2 | 3 | /// An instance of [Logger] for logging debug development details on console. 4 | final logger = Logger( 5 | 'logger', 6 | ); 7 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/kotlin/org/purplegraphite/code/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.purplegraphite.code 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Is the current theme brighness is dark? 4 | bool isDarkTheme(BuildContext context) { 5 | return Theme.of(context).brightness == Brightness.dark; 6 | } 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.5.0-aplha+2104101 4 | 5 | - Feature complete for phase 1 6 | 7 | ## Upcoming 8 | 9 | ## v0.1.0 10 | 11 | ### Version semantic 12 | 13 | - `version + hotfix.`  for stable builds. 14 | - `version - pre-release + `  for any pre-release builds. 15 | -------------------------------------------------------------------------------- /org.purplegraphite.code/.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: f7a6a7906be96d2288f5d63a5a54c515a6e987fe 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /packages/creamy_field/.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: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/creamy_field/example/.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: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/plain_model/ThemeColors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' show MaterialColor; 2 | 3 | class ThemeStyle { 4 | /// Primary MaterialColor of this theme 5 | final MaterialColor color; 6 | 7 | /// Theme name, unique to every theme 8 | final String name; 9 | 10 | const ThemeStyle(this.color, this.name); 11 | 12 | @override 13 | String toString() => name; 14 | @override 15 | int get hashCode => name.hashCode + color.hashCode; 16 | } 17 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/screens/start/controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | enum PreviousProjectLoadingStatus { loading, empty, done } 4 | 5 | class StartScreenController with ChangeNotifier { 6 | PreviousProjectLoadingStatus _previousProjectStatus = 7 | PreviousProjectLoadingStatus.loading; 8 | PreviousProjectLoadingStatus get status => 9 | _previousProjectStatus ?? PreviousProjectLoadingStatus.empty; 10 | 11 | StartScreenController(); 12 | } 13 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /tools/tag_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MAIN_PUBSPEC="org.purplegraphite.code/pubspec.yaml" 3 | CURRENT_VERSION="$(sed -n -e 's/^.*version: //p' ${MAIN_PUBSPEC})" 4 | 5 | echo "Tagging version: $CURRENT_VERSION" 6 | 7 | printf "\nAre you sure you have updated project's version,\nCHANGELOG.md, README.md, Documentation?\n" 8 | read -p "Continue? (Y/n) " -n 1 -r 9 | echo 10 | if [[ $REPLY =~ ^[Nn]$ ]] 11 | then 12 | exit 0 13 | fi 14 | 15 | set -x #echo on 16 | 17 | git tag -a v$CURRENT_VERSION -m "Code $CURRENT_VERSION" 18 | 19 | git push origin v$CURRENT_VERSION -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/generalSettings.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'generalSettings.g.dart'; 4 | 5 | @HiveType(typeId: 1) 6 | class GeneralSettings extends HiveObject { 7 | @HiveField(1) 8 | bool _debuggingEnabled; 9 | 10 | bool get debuggingEnabled => _debuggingEnabled ?? false; 11 | 12 | void set debuggingEnabled(bool debuggingEnabled) { 13 | if (_debuggingEnabled) { 14 | print('Cannot disable debugging'); 15 | return; 16 | } 17 | _debuggingEnabled = debuggingEnabled; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/creamy_field/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: creamy_field 2 | description: Rich Text Editing Field & other components with rich text, selection toolbar & syntax highlight support. Useful in Rich text editors. 3 | version: 0.4.1 4 | homepage: https://github.com/predatorx7/snake_code/tree/packages/creamy_field/packages/creamy_field 5 | 6 | environment: 7 | sdk: '>=2.15.0 <3.0.0' 8 | flutter: '>=2.8.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_highlighter: ^0.1.1 14 | highlighter: ^0.1.1 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^1.0.0 20 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Corners { 4 | /// The border radius of Card & Material widgets in this application. 5 | /// 6 | /// The value is same as [BorderRadius.circular(25.0)]. 7 | static const BorderRadius borderRadius = 8 | const BorderRadius.all(Radius.circular(25.0)); 9 | static const ShapeBorder outlinedShapeBorder = RoundedRectangleBorder( 10 | side: BorderSide( 11 | color: Colors.black, 12 | width: 2, 13 | ), 14 | borderRadius: const BorderRadius.all( 15 | Radius.circular(25), 16 | ), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tools/build_release.sh: -------------------------------------------------------------------------------- 1 | export KEY_ALIAS=$1 2 | export STORE_PASSWORD=$2 3 | echo "Building release.." 4 | if [ -z "$1" ] || [ -z "$2" ]; then 5 | echo 6 | echo "KEY_ALIAS or STORE_PASSWORD has not been specified." 7 | echo "Usage: tools/build_release.sh KEY_ALIAS STORE_PASSWORD" 8 | exit 1 9 | fi 10 | if [ -e org.purplegraphite.code/release.jks ] 11 | then 12 | echo 13 | else 14 | echo 15 | echo "Release keystore file \"org.purplegraphite.code/release.jks\" doesn't exist." 16 | exit 1 17 | fi 18 | cd org.purplegraphite.code; 19 | flutter build apk --release --split-per-abi --split-debug-info=output/symbols; 20 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/app_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | 4 | class ApplicationTitle extends StatelessWidget { 5 | final bool showDark; 6 | 7 | const ApplicationTitle({Key key, @required this.showDark}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Text( 12 | 'Snake code', 13 | style: GoogleFonts.openSans( 14 | fontSize: 28, 15 | fontWeight: FontWeight.w800, 16 | color: showDark ? Colors.black : Colors.white, 17 | letterSpacing: 0.15, 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/syntax_highlighter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/painting.dart'; 3 | import 'syntax_highlighter/creamy_syntax_highlighter.dart' 4 | show CreamySyntaxHighlighter; 5 | import 'syntax_highlighter/dummy_syntax_highlighter.dart' 6 | show DummySyntaxHighlighter; 7 | 8 | /// This is the interface that must be implemented by a Syntax highlighter 9 | /// like [CreamySyntaxHighlighter] & [DummySyntaxHighlighter] 10 | abstract class SyntaxHighlighter { 11 | /// Parses text from [value] & generates syntax highlighted text as list of [TextSpan]. 12 | List parseTextEditingValue(TextEditingValue? value); 13 | } 14 | -------------------------------------------------------------------------------- /tools/status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MAIN_FLUTTER_APP="org.purplegraphite.code" 3 | 4 | 5 | MAIN_PUBSPEC="$MAIN_FLUTTER_APP/pubspec.yaml" 6 | CURRENT_VERSION="$(sed -n -e 's/^.*version: //p' ${MAIN_PUBSPEC})" 7 | 8 | echo "Current version: v$CURRENT_VERSION" 9 | echo "Latest release: $(git describe --abbrev=0)" 10 | echo 11 | printf "Current contributors: \n" 12 | contributors=$(git shortlog -sne --all) 13 | echo "$contributors" 14 | echo 15 | echo "Flutter project size: $(du -sh ./$MAIN_FLUTTER_APP)" 16 | git gc --quiet 17 | git_object_count=$(git count-objects -vH) 18 | SIZE_PACK=$(sed -n -e 's/^.*size-pack: //p' <<< "$git_object_count") 19 | echo "Repository size: $SIZE_PACK (exact disk space consumed)" -------------------------------------------------------------------------------- /org.purplegraphite.code/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.0.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /tools/support/snr.py: -------------------------------------------------------------------------------- 1 | """Helper python script to do "inline search & replace" for update_version.sh""" 2 | 3 | from tempfile import mkstemp 4 | from shutil import move, copymode 5 | from os import fdopen, remove 6 | from sys import argv 7 | 8 | def replace(pattern, subst, file_path): 9 | #Create temp file 10 | fh, abs_path = mkstemp() 11 | with fdopen(fh,'w') as new_file: 12 | with open(file_path) as old_file: 13 | for line in old_file: 14 | new_file.write(line.replace(pattern, subst)) 15 | #Copy the file permissions from the old file to the new file 16 | copymode(file_path, abs_path) 17 | #Remove original file 18 | remove(file_path) 19 | #Move new file 20 | move(abs_path, file_path) 21 | 22 | replace(argv[1], argv[2], argv[3]) -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/checksum.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:crypto/crypto.dart'; 3 | 4 | /// This can be used to compute a sha256 hash checksum function on bytes & files. 5 | class Checksum { 6 | /// This class must not have an instance 7 | Checksum._(); 8 | 9 | static String getBytesDigest(List bytes) { 10 | Digest digest = sha256.convert(bytes); 11 | return digest.toString(); 12 | } 13 | 14 | /// Asynchronously returns file's checksum digest as String 15 | static Future getDigest(File file) async { 16 | return getBytesDigest(await file.readAsBytes()); 17 | } 18 | 19 | /// Synchronously returns file's checksum digest as String 20 | static String getDigestSync(File file) { 21 | return getBytesDigest(file.readAsBytesSync()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /org.purplegraphite.code/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.jks 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | output 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | pubspec.lock 34 | /build/ 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Exceptions to above rules. 46 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 47 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/routing_const.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' show ValueKey; 2 | 3 | const String StartScreenRoute = '/start'; 4 | 5 | /// Update settings with `Provider.of(context, listen: false).updateSettings` 6 | /// before pushing this route 7 | const String EditorScreenRoute = '/editor'; 8 | const String WorkspaceExplorerScreenRoute = 9 | '$EditorScreenRoute/workspace_explorer'; 10 | const String BrowserScreenRoute = '/browser'; 11 | const String HistoryScreenRoute = '/history'; 12 | const String TerminalScreenRoute = '$EditorScreenRoute/terminal'; 13 | const String SettingsScreenRoute = '/settings'; 14 | const String RootRoute = '/'; 15 | 16 | // Here, the value of this key below is compared when widgets are refreshed. If the value matches 17 | // with an existing key in the widget tree, then the widget updates instead of remounting. 18 | const ValueKey RootRouteKey = const ValueKey('rootScreen'); 19 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/about/version.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Version extends StatelessWidget { 4 | final String version; 5 | const Version({Key key, this.version}) : super(key: key); 6 | @override 7 | Widget build(BuildContext context) { 8 | final Color textColor = (Theme.of(context).brightness == Brightness.dark) 9 | ? Colors.white 10 | : Colors.black; 11 | return RichText( 12 | textAlign: TextAlign.center, 13 | text: TextSpan( 14 | text: 'version ', 15 | style: Theme.of(context).textTheme.bodyText2.copyWith( 16 | fontWeight: FontWeight.bold, 17 | fontStyle: FontStyle.italic, 18 | color: Colors.green), 19 | children: [ 20 | TextSpan( 21 | text: version, 22 | style: Theme.of(context) 23 | .textTheme 24 | .bodyText2 25 | .copyWith(fontStyle: FontStyle.italic, color: textColor), 26 | ), 27 | ], 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/service/saver.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/ui/screens/editor/controller.dart'; 2 | 3 | class FileSaving { 4 | static List pages = []; 5 | 6 | void add(EditorTabPage page) { 7 | pages.add(page); 8 | _addOnChangeSaveCallback(page); 9 | } 10 | 11 | void remove(EditorTabPage page) { 12 | pages.remove(page); 13 | } 14 | 15 | bool _isAutoSaveEnabled = true; 16 | 17 | void _addOnChangeSaveCallback(EditorTabPage page) { 18 | if (page.textEditingController != null) { 19 | page.textEditingController.addListener(() async { 20 | if (_isAutoSaveEnabled) { 21 | await save(page); 22 | } 23 | }); 24 | } 25 | } 26 | 27 | Future save(EditorTabPage page) async { 28 | if (page.textEditingController != null) { 29 | final exists = await page.fileEntity.exists(); 30 | if (!exists) return; 31 | await page.fileEntity.writeAsString(page.textEditingController.text); 32 | } 33 | } 34 | 35 | void enableAutoSave() { 36 | _isAutoSaveEnabled = true; 37 | } 38 | 39 | void disableAutoSave() { 40 | _isAutoSaveEnabled = false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/creamy_field.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Mushaheed Syed. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD 3-Clause License that can be 4 | // found in the LICENSE file. 5 | 6 | /// This library provides rich text editing field, controllers, syntax highlighting classes, etc. 7 | 8 | // text controller 9 | export 'src/text_tools/creamy_editing_controller.dart'; 10 | 11 | // text tools 12 | export './src/text_tools/text_selection.dart'; 13 | export './src/text_tools/toolbar_options.dart'; 14 | export './src/text_tools/extensions.dart'; 15 | 16 | // decoration 17 | export './src/decoration/line_indicator.dart' 18 | show LineCountIndicatorDecoration, LineCountIndicator; 19 | export './src/decoration/horizontal_scrollable.dart' show HorizontalScrollable; 20 | 21 | // syntax highlighter 22 | export './src/syntax_highlighter/creamy_syntax_highlighter.dart'; 23 | export './src/syntax_highlighter/language_type.dart'; 24 | export './src/syntax_highlighter/highlighted_theme_type.dart'; 25 | export './src/syntax_highlighter.dart'; 26 | 27 | // Don't keep in releases, ONLY FOR TEST PURPOSES 28 | // export './src/syntax_highlighter/dummy_syntax_highlighter.dart'; 29 | -------------------------------------------------------------------------------- /packages/creamy_field/example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyEditorApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /packages/creamy_field/example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | This example demonstrates a sample usage of `creamy_field` package. 4 | 5 | ## Usage with TextEditingController & Syntax highlighter 6 | 7 | The below example shows the [CreamyEditingController] as [TextEditingController] 8 | which uses CreamySyntaxHighlighter for highlighting syntax of text value from 9 | the text field which uses this controller. 10 | 11 | ```dart 12 | TextEditingController controller = CreamyEditingController( 13 | syntaxHighlighter: CreamySyntaxHighlighter( 14 | language: LanguageType.dart, 15 | theme: HighlightedThemeType.githubTheme, 16 | ), 17 | ); 18 | ``` 19 | 20 | ## CreamyField 21 | 22 | A CreamyField is a text field which supports CreamyEditingController & CreamySyntaxHighlighter. 23 | This text field is similar to Material TextField but will be tailored (expected in future) for 24 | writing rich text, especially for markup & programming languages. 25 | 26 | For now, use it like Material TextField as 27 | 28 | ```dart 29 | CreamyField( 30 | autofocus: true, 31 | controller: controller, 32 | textCapitalization: TextCapitalization.none, 33 | decoration: InputDecoration.collapsed(hintText: 'Start writing'), 34 | ); 35 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | 16 | 17 | ## Summary 18 | 19 | 20 | 21 | ## Motivation 22 | 23 | 24 | 25 | ## Describe alternatives you've considered 26 | 27 | 28 | 29 | ## Additional context 30 | 31 | 32 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/generalSettings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'generalSettings.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class GeneralSettingsAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | GeneralSettings read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return GeneralSettings().._debuggingEnabled = fields[1] as bool; 20 | } 21 | 22 | @override 23 | void write(BinaryWriter writer, GeneralSettings obj) { 24 | writer 25 | ..writeByte(1) 26 | ..writeByte(1) 27 | ..write(obj._debuggingEnabled); 28 | } 29 | 30 | @override 31 | int get hashCode => typeId.hashCode; 32 | 33 | @override 34 | bool operator ==(Object other) => 35 | identical(this, other) || 36 | other is GeneralSettingsAdapter && 37 | runtimeType == other.runtimeType && 38 | typeId == other.typeId; 39 | } 40 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/syntax_highlighter/dummy_syntax_highlighter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:creamy_field/creamy_field.dart'; 5 | 6 | /// This is a dummy implementation of Syntax highlighter for Testing purpose. 7 | /// Ideally, you would implement the `SyntaxHighlighterBase` interface as per your need of highlighting rules. 8 | class DummySyntaxHighlighter implements SyntaxHighlighter { 9 | @override 10 | List parseTextEditingValue(TextEditingValue? tev) { 11 | var texts = tev!.text.split(' '); 12 | 13 | var lsSpans = []; 14 | texts.forEach((text) { 15 | if (text == 'class') { 16 | lsSpans 17 | .add(TextSpan(text: text, style: TextStyle(color: Colors.green))); 18 | } else if (text == 'if' || text == 'else') { 19 | lsSpans.add(TextSpan(text: text, style: TextStyle(color: Colors.blue))); 20 | } else if (text == 'return') { 21 | lsSpans.add(TextSpan(text: text, style: TextStyle(color: Colors.red))); 22 | } else { 23 | lsSpans 24 | .add(TextSpan(text: text, style: TextStyle(color: Colors.black))); 25 | } 26 | lsSpans.add(TextSpan(text: ' ', style: TextStyle(color: Colors.black))); 27 | }); 28 | return lsSpans; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/fileutils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:code/src/models/plain_model/entity.dart'; 5 | import 'package:code/src/utils/permissions.dart'; 6 | 7 | class FileUtils { 8 | static const String _emulatedSdcardRoot = '/storage/emulated/0'; 9 | 10 | static Uri _sdcardRootURI = Uri.file(_emulatedSdcardRoot); 11 | 12 | static Directory primaryRoot = Directory.fromUri(_sdcardRootURI); 13 | 14 | static Future> _dirContents(Directory dir) { 15 | var files = []; 16 | var completer = new Completer>(); 17 | var lister = dir.list(recursive: false); 18 | lister.listen( 19 | (file) async { 20 | var x = Entity(file); 21 | await x.updateStatus(); 22 | files.add(x); 23 | }, 24 | // should also register onError 25 | onDone: () => completer.complete(files), 26 | ); 27 | return completer.future; 28 | } 29 | 30 | static Future createDirectory(Directory directory) async { 31 | return await directory.create(recursive: true); 32 | } 33 | 34 | static Future> listEntities(Directory path) async { 35 | var _c = await _dirContents(path); 36 | _c.sort(); 37 | return _c; 38 | } 39 | 40 | static void checkPerms() async { 41 | await Perms.ask(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/themeSettings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'themeSettings.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class ThemeSettingsAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 2; 12 | 13 | @override 14 | ThemeSettings read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return ThemeSettings( 20 | fields[1] as int, 21 | fields[2] as String, 22 | ); 23 | } 24 | 25 | @override 26 | void write(BinaryWriter writer, ThemeSettings obj) { 27 | writer 28 | ..writeByte(2) 29 | ..writeByte(1) 30 | ..write(obj.themeChoice) 31 | ..writeByte(2) 32 | ..write(obj.themeModeS); 33 | } 34 | 35 | @override 36 | int get hashCode => typeId.hashCode; 37 | 38 | @override 39 | bool operator ==(Object other) => 40 | identical(this, other) || 41 | other is ThemeSettingsAdapter && 42 | runtimeType == other.runtimeType && 43 | typeId == other.typeId; 44 | } 45 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/settings/themeSettings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' show ThemeMode; 2 | import 'package:hive/hive.dart'; 3 | 4 | part 'themeSettings.g.dart'; 5 | 6 | /// Hive Model which has application theme related information 7 | @HiveType(typeId: 2) 8 | class ThemeSettings extends HiveObject { 9 | /// User's choice of theme. defaults to 0. Updated later with User's preference. 10 | @HiveField(1) 11 | int themeChoice; 12 | 13 | /// String representation of [ThemeMode]. 14 | @HiveField(2) 15 | String themeModeS; 16 | 17 | /// Returns ThemeMode. 18 | ThemeMode get themeMode { 19 | return _from(themeModeS); 20 | } 21 | 22 | /// Set Application ThemeMode. 23 | void set themeMode(ThemeMode themeMode) { 24 | themeModeS = themeMode.toString(); 25 | } 26 | 27 | /// TO BE USED WITH GENERATOR. Use [ThemeSettings.manual] instead. 28 | ThemeSettings(this.themeChoice, this.themeModeS); 29 | 30 | ThemeSettings.manual(this.themeChoice, ThemeMode mode) { 31 | themeMode = mode; 32 | } 33 | 34 | ThemeMode _from(String mode) { 35 | switch (mode) { 36 | case "ThemeMode.system": 37 | return ThemeMode.system; 38 | case "ThemeMode.light": 39 | return ThemeMode.light; 40 | case "ThemeMode.dark": 41 | return ThemeMode.dark; 42 | default: 43 | throw Exception(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/popup_menu_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PopupMenuTile extends PopupMenuEntry { 4 | final String value; 5 | final IconData leading; 6 | final Widget title; 7 | final Color color; 8 | final void Function() onTap; 9 | 10 | const PopupMenuTile({ 11 | Key key, 12 | this.value, 13 | this.leading, 14 | this.title, 15 | this.onTap, 16 | this.color, 17 | }) : super(key: key); 18 | 19 | @override 20 | _PopupMenuTileState createState() => _PopupMenuTileState(); 21 | 22 | @override 23 | // TODO: implement height 24 | double get height => throw UnimplementedError(); 25 | 26 | @override 27 | bool represents(dynamic value) { 28 | return true; 29 | } 30 | } 31 | 32 | class _PopupMenuTileState extends State { 33 | @override 34 | Widget build(BuildContext context) { 35 | return PopupMenuItem( 36 | key: ValueKey(widget.value), 37 | value: widget.value, 38 | child: Row( 39 | mainAxisSize: MainAxisSize.min, 40 | children: [ 41 | Padding( 42 | padding: EdgeInsets.only(right: 8, left: 8), 43 | child: Icon( 44 | widget.leading, 45 | color: widget.color, 46 | ), 47 | ), 48 | widget.title, 49 | ], 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/creamy_field/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | publish_to: none 17 | 18 | environment: 19 | sdk: '>=2.12.0 <3.0.0' 20 | 21 | dependencies: 22 | flutter: 23 | sdk: flutter 24 | 25 | # The following adds the Cupertino Icons font to your application. 26 | # Use with the CupertinoIcons class for iOS style icons. 27 | cupertino_icons: ^1.0.2 28 | creamy_field: 29 | path: ../ 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | flutter_lints: ^1.0.0 35 | 36 | flutter: 37 | uses-material-design: true 38 | # dependency_overrides: 39 | # creamy_field: 40 | # path: ../ 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | 14 | 15 | ### Prerequisites 16 | 17 | * [ ] Put an X between the brackets on this line if you have done all of the following: 18 | * Reproduced the problem in Release & Debug Mode 19 | * Checked that your issue isn't already filed: 20 | 21 | ### Description 22 | 23 | 24 | 25 | ### Steps to Reproduce 26 | 27 | 1. 28 | 2. 29 | 3. 30 | 31 | **Expected behavior:** 32 | 33 | 34 | 35 | **Actual behavior:** 36 | 37 | 38 | 39 | **Reproduces how often:** 40 | 41 | 42 | 43 | ### Versions 44 | 45 | 46 | 47 | ### Additional Information 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/acrylic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart' show Colors, Theme; 4 | import 'package:flutter/widgets.dart'; 5 | 6 | /// Adds acrylic glass-like effect to child. 7 | class Acrylic extends StatelessWidget { 8 | /// Acrylic effect is only applied to this child 9 | final Widget child; 10 | final bool enabled; 11 | final bool isDark; 12 | final double blurIntensity; 13 | 14 | /// Changes theme's canvas color 15 | final Widget Function(BuildContext, Widget) builder; 16 | const Acrylic({ 17 | @required this.child, 18 | Key key, 19 | this.builder, 20 | this.enabled = true, 21 | this.isDark, 22 | this.blurIntensity = 5.0, 23 | }) : assert(child != null, 'child should not be null'), 24 | super(key: key); 25 | @override 26 | Widget build(BuildContext context) { 27 | if (!enabled) return child; 28 | final Widget acrylicEnabledchild = BackdropFilter( 29 | filter: ImageFilter.blur(sigmaX: blurIntensity, sigmaY: blurIntensity), 30 | child: Container( 31 | decoration: BoxDecoration( 32 | color: Colors.grey.withOpacity(isDark ? 0.25 : 0.5), 33 | ), 34 | child: child, 35 | ), 36 | ); 37 | if (builder != null) { 38 | return Theme( 39 | data: Theme.of(context).copyWith( 40 | canvasColor: Theme.of(context).canvasColor.withAlpha(0x44), 41 | ), 42 | child: builder(context, acrylicEnabledchild), 43 | ); 44 | } 45 | return acrylicEnabledchild; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Mushaheed Syed 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/creamy_field/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /packages/creamy_field/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Mushaheed Syed 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /packages/creamy_field/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.4.1 4 | 5 | - Make compatible with flutter v2.8 6 | 7 | ## 0.4.0 8 | 9 | - Modified style-usage of public APIs 10 | - Migrate to null-safety 11 | - Removed CreamyField & the classes it depended 12 | - Removed onEnterPress & onBackspacePress method head from SyntaxHighlighter 13 | 14 | ## 0.3.3 15 | 16 | - Made compatible with flutter 1.22 17 | - Removed onEnterPress & onBackspacePress callbacks in CreamyField 18 | - Removed vertical padding from the Text field 19 | 20 | ## 0.3.2 21 | 22 | - Flutter 1.20 capability fix 23 | - Ability to change syntax highlighting based on system ThemeMode & app Brightness 24 | 25 | ## 0.3.1 26 | 27 | - tabSpace can be set in CreamyEditingController 28 | - removed tabSpace from CreamySyntaxHighlighter 29 | - property to add tab to text at baseOffset 30 | 31 | ## 0.3.0+hotfix.2 32 | 33 | - renamed RichEditableText 34 | - added a few tests, expecting more 35 | - changed text description getters. (could be breaking) 36 | 37 | ## 0.3.0+hotfix.1 38 | 39 | - fixed documentation issues 40 | - fixed null exception issues 41 | - added asserts 42 | 43 | ## 0.3.0 44 | 45 | - fix delayed animation of line indicator column 46 | - Custom text selection controls 47 | - extended text selection widget 48 | 49 | ## 0.2.2 50 | 51 | - line indicator in creamy text field 52 | - text field horizontally scrollable 53 | 54 | ## 0.2.1 55 | 56 | - Documentation improvements 57 | 58 | ## 0.2.0 59 | 60 | - Modified Text controller 61 | - Modified new syntax highligher as an implementation of `SyntaxHighlighter` and renamed it with prefix Creamy 62 | 63 | ## 0.1.0 64 | 65 | - CreamyField to be used as a Rich Editing Text Field 66 | - Syntax highlighting support 67 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/utils/permissions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart' show WidgetsFlutterBinding; 4 | import 'package:flutter/services.dart'; 5 | import 'package:permission_handler/permission_handler.dart'; 6 | 7 | class Perms { 8 | static Map requestedResult; 9 | static final Permission _storageAccessPerm = Permission.storage; 10 | 11 | // TODO: make a dialog to describe why we need permission 12 | /// Requests permissions only once. Strictly requires user to accept, on denial will exit the app. 13 | static Future askOnce() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | var status = await _storageAccessPerm.status; 16 | if (!status.isGranted) { 17 | // We didn't have permissions yet. 18 | await ask(); 19 | } 20 | // async completed 21 | return true; 22 | } 23 | 24 | static Future ask() async { 25 | //Requesting multiple permissions at once. 26 | requestedResult = await [_storageAccessPerm].request(); 27 | // Iterating map to check permissions 28 | requestedResult.forEach((perm, permStatus) async { 29 | if (await perm.request().isGranted) { 30 | // Either the permission was already granted before or the user just granted it. 31 | } else { 32 | // Not granted, so opening settings 33 | openAppSettings(); 34 | } 35 | await recheck(perm); 36 | }); 37 | } 38 | 39 | static Future recheck(Permission perm) async { 40 | // Re-checking & Re-requesting 41 | if (!(await perm.request().isGranted)) { 42 | // Exit App 43 | await SystemNavigator.pop(animated: true); 44 | exit(1); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/creamy_field/example/.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 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | 74 | android/ 75 | ios/ 76 | web/ -------------------------------------------------------------------------------- /.idea/code.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/creamy_field/.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 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | doc/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | example/android/ 75 | .vscode/ 76 | 77 | example/ios/Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/history.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive/hive.dart'; 3 | 4 | part 'history.g.dart'; 5 | 6 | /// UNIMPLEMENTED for a list of open files 7 | @HiveType(typeId: 6) 8 | class FileModificationHistory extends HiveObject { 9 | @HiveField(1) 10 | final String absolutePath; 11 | @HiveField(2) 12 | DateTime _lastModified; 13 | 14 | DateTime get lastModified => _lastModified; 15 | 16 | void updateLastModified() { 17 | _lastModified = DateTime.now(); 18 | } 19 | 20 | @HiveField(3) 21 | final double scrollOffset; 22 | @HiveField(4) 23 | final int cursorOffset; 24 | 25 | FileModificationHistory({ 26 | this.absolutePath, 27 | this.scrollOffset, 28 | this.cursorOffset, 29 | }); 30 | 31 | void setlatestModified() { 32 | _lastModified = DateTime.now(); 33 | } 34 | } 35 | 36 | @HiveType(typeId: 5) 37 | class History extends HiveObject with Comparable { 38 | @HiveField(1) 39 | final String workspacePath; 40 | 41 | @HiveField(2) 42 | FileModificationHistory lastModifiedFileDetails; 43 | 44 | @HiveField(3) 45 | DateTime _lastModified; 46 | 47 | DateTime get lastModified => _lastModified; 48 | 49 | History({@required this.workspacePath}); 50 | 51 | void updateLastModifiedDateTime() { 52 | _lastModified = DateTime.now(); 53 | } 54 | 55 | @override 56 | Future save() async { 57 | updateLastModifiedDateTime(); 58 | if (lastModifiedFileDetails != null) await lastModifiedFileDetails.save(); 59 | return await super.save(); 60 | } 61 | 62 | static bool compareByDate = true; 63 | 64 | @override 65 | int compareTo(History other) { 66 | if (compareByDate ?? true) { 67 | return this.lastModified.compareTo(other.lastModified); 68 | } else { 69 | return this.workspacePath.compareTo(other.workspacePath); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:hive_flutter/hive_flutter.dart'; 4 | 5 | /// A repository which holds a Hive [Box] with helper functions. 6 | class Repository { 7 | final TypeAdapter adapter; 8 | final Box box; 9 | final String boxName; 10 | 11 | M get first => box.values.first; 12 | 13 | Iterable iterable() => box.values; 14 | 15 | bool get isRepositoryEmpty => box?.isEmpty ?? true; 16 | 17 | /// Creates a [Repository] to wrap an open [box] of [adapter] with name [boxName] 18 | Repository(this.boxName, this.adapter, this.box); 19 | 20 | static bool _isHiveInitialized = false; 21 | 22 | /// Initiates Hive for flutter and returns a Hive [Box] wrapped with [Repository]. 23 | /// 24 | /// Registers M's [adapter] and opens [M] box of name [boxName]. (Creates if doesn't exist) 25 | /// 26 | /// Either register Type adapter with [register] or provide the TypeAdapter as the parameter in [adapter] 27 | static Future> get(String boxName, 28 | [TypeAdapter adapter]) async { 29 | if (!_isHiveInitialized) { 30 | await Hive.initFlutter(); 31 | _isHiveInitialized = true; 32 | } 33 | 34 | register(adapter); 35 | final Box box = await Hive.openBox(boxName); 36 | return Repository(boxName, adapter, box); 37 | } 38 | 39 | static void register(TypeAdapter adapter) { 40 | final _isRegistered = Hive.isAdapterRegistered(adapter.typeId); 41 | if (_isRegistered) return; 42 | Hive.registerAdapter(adapter); 43 | } 44 | 45 | /// Check if box is open 46 | bool isBoxOpen() { 47 | return box?.isOpen ?? false; 48 | } 49 | 50 | /// Subscribe to Stream of BoxEvent which is triggered when a read/write 51 | /// operation is performed on [box] 52 | StreamSubscription listenStream(void Function(BoxEvent) onData) { 53 | return this.box.watch().listen(onData); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/plain_model/entity.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:path/path.dart' as path; 3 | 4 | /// A FileSystemEntity wrapper 5 | class Entity extends Comparable { 6 | String get id => absolutePath; 7 | String get name => basename; 8 | String basename; 9 | String absolutePath; 10 | FileSystemEntity entity; 11 | FileStat stat; 12 | 13 | /// Scroll Offset, initially 0 14 | double scrollOffset = 0.0; 15 | 16 | Entity(this.entity) { 17 | if (!this.entity.isAbsolute) { 18 | this.entity = this.entity.absolute; 19 | } 20 | absolutePath = this.entity.path; 21 | basename = path.basename(this.entity.path); 22 | } 23 | 24 | Entity.blank() 25 | : absolutePath = DateTime.now().millisecondsSinceEpoch.toString(), 26 | basename = 'untitled'; 27 | 28 | void updateStatus() async { 29 | this.stat = await entity.stat(); 30 | } 31 | 32 | @override 33 | bool operator ==(Object other) { 34 | if (other is Entity) { 35 | return this.absolutePath == other.absolutePath; 36 | } else 37 | return false; 38 | } 39 | 40 | static String _toLowercaseFirstChar(String string) { 41 | return "${string[0].toLowerCase()}${string.substring(1)}"; 42 | } 43 | 44 | @override 45 | int compareTo(Entity other) { 46 | // Converting 1st char to lowercase 47 | String a = _toLowercaseFirstChar(this.basename); 48 | String b = _toLowercaseFirstChar(other.basename); 49 | 50 | if (a[0] == '.' || b[0] == '.') { 51 | if (a[0] == '.' && b[0] == '.') { 52 | a = _toLowercaseFirstChar(a.substring(1)); 53 | b = _toLowercaseFirstChar(b.substring(1)); 54 | } else if (a[0] == '.') { 55 | return 1; 56 | } else if (b[0] == '.') { 57 | return -1; 58 | } 59 | } 60 | 61 | if (a == b) return 0; 62 | var _c = [a, b]; 63 | _c.sort(); 64 | if (_c[0] == a) { 65 | return -1; 66 | } else { 67 | return 1; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/text_tools/extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show TextEditingController; 2 | 3 | /// Extension methods on [TextEditingController] for additional information about the editing text. 4 | 5 | extension CreamyTextFieldExtensions on TextEditingController { 6 | /// The text currently under selection 7 | String get selectedText => selection.textInside(text); 8 | 9 | /// The text before the current text selection 10 | String get beforeSelectedText => selection.textBefore(text); 11 | 12 | /// The text after the current text selection 13 | String get afterSelectedText => selection.textAfter(text); 14 | 15 | /// Total number of lines in the [text] 16 | int get totalLineCount => text.split('\n').length; 17 | 18 | /// The line at which end cursor lies 19 | int get atLine => beforeSelectedText.split('\n').length; 20 | 21 | /// The column at which the end cursor is at. 22 | int get atColumn { 23 | int _extent = selection.extentOffset; 24 | String precursorText = text.substring(0, _extent); 25 | return (_extent - precursorText.lastIndexOf('\n')); 26 | } 27 | 28 | /// The column index where the selection extent ends. 29 | /// Same as [atColumn]. 30 | int get extentColumn { 31 | return atColumn; 32 | } 33 | 34 | /// The column index at which the selection (base) begins. 35 | int get baseColumn { 36 | int _base = selection.baseOffset; 37 | String precursorText = text.substring(0, _base); 38 | return (_base - precursorText.lastIndexOf('\n')); 39 | } 40 | 41 | // TODO: add extensions to TextEditingValue 42 | Map get textDescriptionMap => { 43 | 'text': text, 44 | 'beforeSelectedText': beforeSelectedText, 45 | 'afterSelectedText': afterSelectedText, 46 | 'selectedText': selectedText, 47 | 'totalLines': totalLineCount, 48 | 'atLine': atLine, 49 | 'atColumn': atColumn, 50 | 'baseColumn': baseColumn, 51 | 'extentColumn': extentColumn, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Flutter build test 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "analysis" 16 | buildTests: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | # Setup Java environment in order to build the Android app. 25 | - uses: actions/setup-java@v1 26 | with: 27 | java-version: "12.x" 28 | - uses: subosito/flutter-action@v1.5.3 29 | with: 30 | # The Flutter version to make available on the path 31 | flutter-version: 2.8.1 # optional 32 | # The Flutter build release channel 33 | channel: stable # optional, default is stable 34 | 35 | - name: Install NDK 36 | run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;21.0.6113669" --sdk_root=${ANDROID_SDK_ROOT} 37 | 38 | - name: Building debug apk 39 | run: | 40 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 41 | flutter build apk --debug --target-platform=android-arm64 --split-debug-info=output/symbols; 42 | 43 | - name: Uploading artifacts to zip 44 | uses: actions/upload-artifact@v1.0.0 45 | with: 46 | # Artifact name 47 | name: org.purplegraphite.code-debug-android_apks.zip 48 | # Directory containing files to upload 49 | path: org.purplegraphite.code/build/app/outputs/apk/debug/ 50 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/buttons/action_tabs_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/ui/screens/editor/controller.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | class ActionsTabButton extends StatelessWidget { 6 | /// Return proper tab button label based on number of tabs open 7 | static String changeLabel(Iterable tabsOpen) { 8 | if (tabsOpen?.isEmpty ?? true) { 9 | return '0'; 10 | } else if (tabsOpen.length > 99) { 11 | return ':P'; 12 | } else { 13 | return tabsOpen.length.toString(); 14 | } 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final EditorController editorController = 20 | Provider.of(context); 21 | final ThemeData theme = Theme.of(context); 22 | final bool isDark = theme.brightness == Brightness.dark; 23 | Color appbarAccent = isDark ? Colors.amber : Colors.white; 24 | final tabs = editorController.tabs; 25 | String label = changeLabel(tabs); 26 | return Tooltip( 27 | message: 'change tabs', 28 | child: IconButton( 29 | onPressed: () { 30 | Scaffold.of(context).openEndDrawer(); 31 | }, 32 | icon: Center( 33 | child: Container( 34 | padding: const EdgeInsets.all(2), 35 | alignment: Alignment.center, 36 | decoration: BoxDecoration( 37 | borderRadius: BorderRadius.circular(6), // To match with chrome 38 | // borderRadius: BorderRadius.circular(25), // Original 39 | border: Border.all( 40 | width: 2, color: isDark ? appbarAccent : Colors.white), 41 | ), 42 | constraints: BoxConstraints.tight(const Size(25.0, 25.0)), 43 | child: Text( 44 | '$label', 45 | style: TextStyle( 46 | color: appbarAccent, 47 | fontWeight: isDark ? FontWeight.w800 : FontWeight.bold, 48 | fontSize: 10, 49 | ), 50 | ), 51 | ), 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/decoration/horizontal_scrollable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | 5 | /// Makes the child horizontally scrollable 6 | class HorizontalScrollable extends StatelessWidget { 7 | /// Scrolling horizontally would be only enabled if this value is true 8 | final bool beScrollable; 9 | 10 | final Widget? child; 11 | 12 | final ScrollController? scrollController; 13 | 14 | /// In the future, this controller from a child TextField will be used to determine horizontal scroll extent. 15 | final TextEditingController? textEditingController; 16 | 17 | final ScrollPhysics physics; 18 | 19 | /// How much this widget should get horizontally scrolled. 20 | /// Defaults to 2000 21 | // TODO: Change this static value with the calculation horizontal scroll extent based on the pixel length of text of the longest line in textField. 22 | final double horizontalScrollExtent; 23 | 24 | /// Wrap with Expanded widget 25 | final bool expand; 26 | 27 | /// Scroll padding of the text field under this scrollable 28 | /// defaults to `EdgeInsets.zero` 29 | final EdgeInsetsGeometry padding; 30 | 31 | /// Makes the child horizontally scrollable 32 | const HorizontalScrollable({ 33 | Key? key, 34 | this.textEditingController, 35 | this.scrollController, 36 | this.physics = const ClampingScrollPhysics(), 37 | this.child, 38 | this.beScrollable = true, 39 | this.horizontalScrollExtent = 2000, 40 | this.padding = EdgeInsets.zero, 41 | this.expand = false, 42 | }) : assert(horizontalScrollExtent > 0, 43 | 'horizontal scroll extent should not be less than 1'), 44 | super(key: key); 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | if (!beScrollable) return child!; 49 | 50 | final _scrollable = SingleChildScrollView( 51 | padding: padding, 52 | scrollDirection: Axis.horizontal, 53 | controller: scrollController, 54 | physics: physics, 55 | dragStartBehavior: DragStartBehavior.down, 56 | child: ConstrainedBox( 57 | constraints: BoxConstraints.expand( 58 | width: horizontalScrollExtent, 59 | ), 60 | child: child, 61 | ), 62 | ); 63 | 64 | if (!expand) return _scrollable; 65 | 66 | return Expanded( 67 | child: _scrollable, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/strings.dart: -------------------------------------------------------------------------------- 1 | class Strings { 2 | /// Application's version. 3 | /// Don't edit manually, use `/tools/update_version.sh`. 4 | static const String applicationVersion = '0.5.1-alpha+210410'; 5 | 6 | static const String applicationTitle = 'Snake code'; 7 | 8 | static var applicationLegalese = _legalese; 9 | 10 | static var aboutUs = 'about'; 11 | 12 | static String contactUs = 'Contact us'; 13 | 14 | static String viewLicenses = 'View licenses'; 15 | } 16 | 17 | class StorageBoxNames { 18 | StorageBoxNames._(); 19 | 20 | static const _base = 'org.purplegraphite.code'; 21 | static const HISTORY = '$_base-history'; 22 | static const THEME_SETTINGS = '$_base-themesettings'; 23 | static const FILE_MODIFICATION_HISTORY = '$_base-fileModificationHistory'; 24 | } 25 | 26 | const _legalese = """BSD 3-Clause License 27 | 28 | Copyright (c) 2020, Mushaheed Syed 29 | All rights reserved. 30 | 31 | Redistribution and use in source and binary forms, with or without 32 | modification, are permitted provided that the following conditions are met: 33 | 34 | 1. Redistributions of source code must retain the above copyright notice, this 35 | list of conditions and the following disclaimer. 36 | 37 | 2. Redistributions in binary form must reproduce the above copyright notice, 38 | this list of conditions and the following disclaimer in the documentation 39 | and/or other materials provided with the distribution. 40 | 41 | 3. Neither the name of the copyright holder nor the names of its 42 | contributors may be used to endorse or promote products derived from 43 | this software without specific prior written permission. 44 | 45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 46 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 48 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 49 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 50 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 52 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 53 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 54 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""; 55 | -------------------------------------------------------------------------------- /tools/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$1 4 | 5 | # == EDIT PATHS IN THESE VARIABLES ==== 6 | MAIN_PUBSPEC="org.purplegraphite.code/pubspec.yaml" 7 | CONST_STRING_DART="./org.purplegraphite.code/lib/src/commons/strings.dart" 8 | # == END ============================== 9 | 10 | LAST_VERSION="$(sed -n -e 's/^.*version: //p' ${MAIN_PUBSPEC})" 11 | 12 | echo "The previous version was: $LAST_VERSION" 13 | 14 | if [ -z "$VERSION" ]; then 15 | echo 16 | echo "No version specified." 17 | echo "Usage: tools/update_version.sh NEW_VERSION_NUMBER" 18 | exit 1 19 | fi 20 | 21 | printf "\nThis will change project's version from $LAST_VERSION to $VERSION\n" 22 | read -p "Are you sure you want to continue? (Y/n) " -n 1 -r 23 | echo 24 | if [[ $REPLY =~ ^[Nn]$ ]] 25 | then 26 | exit 0 27 | fi 28 | 29 | printf "\nUpdating pubspec versions.." 30 | 31 | # perl -pi -e "s/^(\\W*version:) $LAST_VERSION/\$1 $VERSION/g" $PUBSPECS 32 | python tools/support/snr.py "version: ${LAST_VERSION}" "version: $VERSION" $MAIN_PUBSPEC 33 | # perl -pi -e "s/^(\\W*version:) [0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 34 | 35 | printf "\nUpdating versions in app source code where required.." 36 | # Update version defined in the source code in app. 37 | # perl -pi -e "s/^(\\W*static const String applicationVersion =) '[0-9.beta-]+'/\$1 '$VERSION'/g" $CONST_STRING_DART 38 | python tools/support/snr.py "static const String applicationVersion = '${LAST_VERSION}'" "static const String applicationVersion = '$VERSION'" $CONST_STRING_DART 39 | 40 | NEW_VERSION="$(sed -n -e 's/^.*version: //p' ${MAIN_PUBSPEC})" 41 | 42 | printf "\n\nUpdated version to: $NEW_VERSION\n" 43 | 44 | # == NOT REQUIRED IN THIS PROJECT ==== 45 | # # Update the version of all packages/applications. 46 | 47 | # # If you add a package that is version locked, please add it to this list as 48 | # # "./org.floraprobe/pubspec.yaml \ 49 | # # ./packages/" 50 | # PUBSPECS="./${MAIN_PUBSPEC}" 51 | 52 | # pushd packages 53 | 54 | # # We could use LAST_VERSION instead of allowing any previous version 55 | 56 | # # Update all references to package versions 57 | # perl -pi -e "s/^(\\W*package1:) \\^?[0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 58 | # perl -pi -e "s/^(\\W*package2:) \\^?[0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 59 | # perl -pi -e "s/^(\\W*package3:) \\^?[0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 60 | # perl -pi -e "s/^(\\W*package4:) \\^?[0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 61 | # perl -pi -e "s/^(\\W*package5:) \\^?[0-9.beta-]+/\$1 $VERSION/g" $PUBSPECS 62 | 63 | # popd 64 | # == END ==== -------------------------------------------------------------------------------- /org.purplegraphite.code/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 flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | ndkVersion "21.0.6113669" 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | checkReleaseBuilds false 39 | } 40 | 41 | defaultConfig { 42 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 43 | applicationId "org.purplegraphite.code" 44 | minSdkVersion 16 45 | targetSdkVersion 29 46 | versionCode flutterVersionCode.toInteger() 47 | versionName flutterVersionName 48 | } 49 | 50 | signingConfigs { 51 | release { 52 | storeFile file("../../release.jks") 53 | storePassword = "$System.env.STORE_PASSWORD" 54 | keyAlias = "$System.env.KEY_ALIAS" 55 | keyPassword = "$System.env.STORE_PASSWORD" 56 | } 57 | } 58 | 59 | buildTypes { 60 | release { 61 | // org.purplegraphite.code's own signing config for the release build. 62 | // Signing with the release keys, so `flutter run --release` works. 63 | signingConfig signingConfigs.release 64 | // Comment the above line and comment out line below when debugging without keys 65 | // signingConfig signingConfigs.debug 66 | } 67 | } 68 | } 69 | 70 | flutter { 71 | source '../..' 72 | } 73 | 74 | dependencies { 75 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 76 | } 77 | -------------------------------------------------------------------------------- /org.purplegraphite.code/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:code/src/utils/checksum.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:path/path.dart' as path; 7 | 8 | void prepareCollisionTestFiles(File file, String message) { 9 | if (!file.existsSync()) { 10 | file.createSync(); 11 | } 12 | file.writeAsStringSync( 13 | message, 14 | mode: FileMode.writeOnly, 15 | encoding: utf8, 16 | ); 17 | } 18 | 19 | void main() { 20 | group('File tests for collision', () { 21 | String currentDirectory; 22 | String test1Path, test2Path, test3Path; 23 | File test1, test2, test3; 24 | setUp(() { 25 | currentDirectory = Directory.current.path; 26 | test1Path = path.join(currentDirectory, 'test1.txt'); 27 | test2Path = path.join(currentDirectory, 'test2.txt'); 28 | test3Path = path.join(currentDirectory, 'test3.txt'); 29 | test1 = File(test1Path); 30 | test2 = File(test2Path); 31 | test3 = File(test3Path); 32 | prepareCollisionTestFiles(test1, 'This is message 1'); 33 | prepareCollisionTestFiles(test2, 'It\'s message 2'); 34 | prepareCollisionTestFiles(test3, 'This is message 1'); 35 | }); 36 | test('by comparing a file\'s checksum with another file with same content', 37 | () async { 38 | final String reason = 39 | 'Checksums of same file (calculated twice) do not match!'; 40 | final String digest1 = await Checksum.getDigest(test1); 41 | final String digest2 = await Checksum.getDigest(test3); 42 | expect(digest1, digest2, reason: reason); 43 | final String digest3 = Checksum.getDigestSync(test1); 44 | final String digest4 = Checksum.getDigestSync(test3); 45 | expect(digest3, digest4, reason: reason); 46 | }); 47 | test('by comparing a file\'s checksum with other file\'s checksum', 48 | () async { 49 | final String reason = 'Checksums of both file matches!'; 50 | final String digest1 = await Checksum.getDigest(test1); 51 | final String digest2 = await Checksum.getDigest(test2); 52 | expect(digest1 != digest2, true, reason: reason); 53 | expect(digest1 == digest2, false, reason: reason); 54 | final String digest3 = Checksum.getDigestSync(test1); 55 | final String digest4 = Checksum.getDigestSync(test2); 56 | expect(digest3 != digest4, true, reason: reason); 57 | expect(digest3 == digest4, false, reason: reason); 58 | }); 59 | tearDown(() { 60 | test1.delete(); 61 | test2.delete(); 62 | test3.delete(); 63 | }); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/view_model/browser_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:code/src/models/plain_model/entity.dart'; 4 | import 'package:code/src/utils/fileutils.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:path/path.dart' as path; 7 | 8 | class BrowserController extends ChangeNotifier { 9 | Directory _current; 10 | 11 | Directory get current => _current; 12 | 13 | List _currentEntities = []; 14 | List get currentEntities => _currentEntities; 15 | bool _stopLoading = false; 16 | 17 | bool get stopLoading => _stopLoading; 18 | List _recentlyCreatedFolder = []; 19 | 20 | // List of paths of newly created folder 21 | List get recentlyCreatedFolder => _recentlyCreatedFolder; 22 | 23 | /// Creates folder and updates listeners 24 | void createFolderAndAddToRecent( 25 | BuildContext context, String recentlyCreatedFolderBasename) async { 26 | var recentlyCreatedFolderPath = 27 | path.join(current.path, recentlyCreatedFolderBasename); 28 | await Directory.fromUri( 29 | Uri( 30 | path: recentlyCreatedFolderPath, 31 | ), 32 | ).create(); 33 | _recentlyCreatedFolder.add(recentlyCreatedFolderPath); 34 | notifyListeners(); 35 | await updateEntityList(context); 36 | } 37 | 38 | Future createFileAndAddToRecent( 39 | BuildContext context, String recentlyCreatedFolderBasename) async { 40 | var recentlyCreatedFolderPath = 41 | path.join(current.path, recentlyCreatedFolderBasename); 42 | final _file = await File.fromUri( 43 | Uri( 44 | path: recentlyCreatedFolderPath, 45 | ), 46 | ).create(); 47 | _recentlyCreatedFolder.add(recentlyCreatedFolderPath); 48 | notifyListeners(); 49 | await updateEntityList(context); 50 | return _file; 51 | } 52 | 53 | Future setCurrent(Directory dir) async { 54 | _current = dir; 55 | _currentEntities = await FileUtils.listEntities(_current); 56 | _stopLoading = true; 57 | notifyListeners(); 58 | } 59 | 60 | Future updateEntityList(BuildContext context) async { 61 | if (!(await current.exists())) { 62 | Navigator.of(context).maybePop(); 63 | return; 64 | } 65 | var _c = await FileUtils.listEntities(_current); 66 | if (_c.length == _currentEntities.length) { 67 | var same = true; 68 | for (var i = 0; i < _c.length; i++) { 69 | same = _c[i] == _currentEntities[i]; 70 | if (!same) break; 71 | } 72 | if (same) return; 73 | } 74 | 75 | _currentEntities = _c; 76 | notifyListeners(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/provider/history.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/common/strings.dart'; 2 | import 'package:code/src/models/hive/history.dart'; 3 | import 'package:code/src/models/hive/repository.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:hive/hive.dart' show Box, Hive; 6 | 7 | class RecentHistoryProvider extends ChangeNotifier { 8 | RecentHistoryProvider() { 9 | _setup(); 10 | } 11 | 12 | Repository _history; 13 | 14 | Box get box => _history.box; 15 | 16 | bool get hasHistory { 17 | return !(_history?.isRepositoryEmpty ?? true); 18 | } 19 | 20 | bool get isInitialized => _history != null; 21 | 22 | /// Sets up & Initializes preferences. 23 | Future _setup() async { 24 | Hive.registerAdapter( 25 | FileModificationHistoryAdapter()); 26 | _history = await Repository.get( 27 | StorageBoxNames.HISTORY, HistoryAdapter()); 28 | 29 | _history.listenStream((_) { 30 | notifyListeners(); 31 | }); 32 | notifyListeners(); 33 | } 34 | 35 | History get(String path) { 36 | return _history.box.get(path); 37 | } 38 | 39 | List searchFor(String key) { 40 | final _histories = []; 41 | for (var item in _history.box.values) { 42 | final _itemID = item.workspacePath; 43 | if (key == _itemID) { 44 | _histories.insert(0, item); 45 | return _histories; 46 | } else if (_itemID.contains(key)) { 47 | _histories.add(item); 48 | } 49 | } 50 | return _histories; 51 | } 52 | 53 | List getHistories() { 54 | final _result = _history?.box?.values?.toList() ?? []; 55 | _result.sort(); 56 | return _result; 57 | } 58 | 59 | void add( 60 | String path, [ 61 | FileModificationHistory lastModifiedFileDetails, 62 | ]) async { 63 | final _projectHistory = History(workspacePath: path); 64 | _projectHistory.lastModifiedFileDetails = lastModifiedFileDetails; 65 | _projectHistory.updateLastModifiedDateTime(); 66 | await _history.box.put(_projectHistory.workspacePath, _projectHistory); 67 | print( 68 | 'SAVED: ${_history.box.get(_projectHistory.workspacePath).workspacePath}'); 69 | if (_projectHistory.isInBox) await _projectHistory.save(); 70 | } 71 | 72 | Future remove(String path) async { 73 | if (box.containsKey(path)) { 74 | await box.delete(path); 75 | return true; 76 | } else { 77 | return false; 78 | } 79 | } 80 | 81 | Future update(History history) async { 82 | await box.put(history.workspacePath, history); 83 | await history.save(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Make release 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | tags: 10 | - "v*" 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | assembleRelease: 16 | # The type of runner that the job will run on 17 | runs-on: ubuntu-latest 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v2 23 | # Setup Java environment in order to build the Android app. 24 | - uses: actions/setup-java@v1 25 | with: 26 | java-version: "12.x" 27 | - uses: subosito/flutter-action@v1.5.3 28 | with: 29 | # The Flutter version to make available on the path 30 | flutter-version: 2.8.1 # optional 31 | # The Flutter build release channel 32 | channel: stable # optional, default is stable 33 | 34 | - name: Install NDK 35 | run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;21.0.6113669" --sdk_root=${ANDROID_SDK_ROOT} 36 | 37 | - name: Restore release key 38 | run: | 39 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 40 | echo "${{ secrets.KEYSTORE_JKS_ASC }}" > release.jks.asc; 41 | echo "Decrypting keystore"; 42 | gpg -d --passphrase "${{ secrets.STORE_PASSWORD }}" --batch release.jks.asc > release.jks; 43 | rm release.jks.asc; 44 | 45 | - name: Build APKs 46 | env: 47 | KEY_ALIAS: ${{ secrets.KEY_ALIAS }} 48 | STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }} 49 | # https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split 50 | run: | 51 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 52 | flutter build apk --release --split-per-abi --split-debug-info=output/symbols; 53 | 54 | - name: Upload release 55 | uses: softprops/action-gh-release@v1 56 | with: 57 | prerelease: true 58 | body_path: RELEASE_NOTES.md 59 | name: "Pre-release" 60 | files: | 61 | org.purplegraphite.code/build/app/outputs/apk/release/app-arm64-v8a-release.apk 62 | org.purplegraphite.code/build/app/outputs/apk/release/app-armeabi-v7a-release.apk 63 | org.purplegraphite.code/build/app/outputs/apk/release/app-x86_64-release.apk 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/hive/history.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'history.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class FileModificationHistoryAdapter 10 | extends TypeAdapter { 11 | @override 12 | final int typeId = 6; 13 | 14 | @override 15 | FileModificationHistory read(BinaryReader reader) { 16 | final numOfFields = reader.readByte(); 17 | final fields = { 18 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 19 | }; 20 | return FileModificationHistory( 21 | absolutePath: fields[1] as String, 22 | scrollOffset: fields[3] as double, 23 | cursorOffset: fields[4] as int, 24 | ).._lastModified = fields[2] as DateTime; 25 | } 26 | 27 | @override 28 | void write(BinaryWriter writer, FileModificationHistory obj) { 29 | writer 30 | ..writeByte(4) 31 | ..writeByte(1) 32 | ..write(obj.absolutePath) 33 | ..writeByte(2) 34 | ..write(obj._lastModified) 35 | ..writeByte(3) 36 | ..write(obj.scrollOffset) 37 | ..writeByte(4) 38 | ..write(obj.cursorOffset); 39 | } 40 | 41 | @override 42 | int get hashCode => typeId.hashCode; 43 | 44 | @override 45 | bool operator ==(Object other) => 46 | identical(this, other) || 47 | other is FileModificationHistoryAdapter && 48 | runtimeType == other.runtimeType && 49 | typeId == other.typeId; 50 | } 51 | 52 | class HistoryAdapter extends TypeAdapter { 53 | @override 54 | final int typeId = 5; 55 | 56 | @override 57 | History read(BinaryReader reader) { 58 | final numOfFields = reader.readByte(); 59 | final fields = { 60 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 61 | }; 62 | return History( 63 | workspacePath: fields[1] as String, 64 | ) 65 | ..lastModifiedFileDetails = fields[2] as FileModificationHistory 66 | .._lastModified = fields[3] as DateTime; 67 | } 68 | 69 | @override 70 | void write(BinaryWriter writer, History obj) { 71 | writer 72 | ..writeByte(3) 73 | ..writeByte(1) 74 | ..write(obj.workspacePath) 75 | ..writeByte(2) 76 | ..write(obj.lastModifiedFileDetails) 77 | ..writeByte(3) 78 | ..write(obj._lastModified); 79 | } 80 | 81 | @override 82 | int get hashCode => typeId.hashCode; 83 | 84 | @override 85 | bool operator ==(Object other) => 86 | identical(this, other) || 87 | other is HistoryAdapter && 88 | runtimeType == other.runtimeType && 89 | typeId == other.typeId; 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/main_package.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Flutter package analysis 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ packages/* ] 10 | pull_request: 11 | branches: [ packages/*, master] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "analysis" 16 | package_analysis: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | # Setup Java environment in order to build the Android app. 25 | - uses: actions/setup-java@v1 26 | with: 27 | java-version: "12.x" 28 | - uses: subosito/flutter-action@v1.5.3 29 | with: 30 | # The Flutter version to make available on the path 31 | flutter-version: 2.8.1 # optional 32 | # The Flutter build release channel 33 | channel: stable # optional, default is stable 34 | 35 | - name: Flutter doctor 36 | run: flutter doctor -v; 37 | 38 | - name: Refreshing dependencies 39 | run: | 40 | cd $GITHUB_WORKSPACE/packages/creamy_field; 41 | flutter pub get; 42 | 43 | - name: Flutter code formatting check 44 | run: | 45 | cd $GITHUB_WORKSPACE/packages/creamy_field; 46 | flutter format --set-exit-if-changed .; 47 | 48 | - name: analyze issues 49 | run: | 50 | cd $GITHUB_WORKSPACE/packages/creamy_field; 51 | flutter analyze .; 52 | 53 | package_tests: 54 | # The type of runner that the job will run on 55 | runs-on: ubuntu-latest 56 | 57 | # Steps represent a sequence of tasks that will be executed as part of the job 58 | steps: 59 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 60 | - uses: actions/checkout@v2 61 | # Setup Java environment in order to build the Android app. 62 | - uses: actions/setup-java@v1 63 | with: 64 | java-version: "12.x" 65 | - uses: subosito/flutter-action@v1.5.3 66 | with: 67 | # The Flutter version to make available on the path 68 | flutter-version: 2.8.1 # optional 69 | # The Flutter build release channel 70 | channel: stable # optional, default is stable 71 | - name: run tests 72 | run: | 73 | cd $GITHUB_WORKSPACE/packages/creamy_field; 74 | flutter test; 75 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Flutter analysis 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master, dev/*] 10 | pull_request: 11 | branches: [master, dev/*] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "analysis" 16 | analysis: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | # Setup Java environment in order to build the Android app. 25 | - uses: actions/setup-java@v1 26 | with: 27 | java-version: "12.x" 28 | - uses: subosito/flutter-action@v1.5.3 29 | with: 30 | # The Flutter version to make available on the path 31 | flutter-version: 2.8.1 # optional 32 | # The Flutter build release channel 33 | channel: stable # optional, default is stable 34 | 35 | - name: Flutter doctor 36 | run: | 37 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 38 | flutter doctor -v; 39 | 40 | - name: Refreshing dependencies 41 | run: | 42 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 43 | flutter pub get; 44 | 45 | - name: Flutter code formatting check 46 | run: | 47 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 48 | flutter format --set-exit-if-changed .; 49 | 50 | - name: analyze issues 51 | run: | 52 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 53 | flutter analyze .; 54 | 55 | tests: 56 | # The type of runner that the job will run on 57 | runs-on: ubuntu-latest 58 | 59 | # Steps represent a sequence of tasks that will be executed as part of the job 60 | steps: 61 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 62 | - uses: actions/checkout@v2 63 | # Setup Java environment in order to build the Android app. 64 | - uses: actions/setup-java@v1 65 | with: 66 | java-version: "12.x" 67 | - uses: subosito/flutter-action@v1.5.3 68 | with: 69 | # The Flutter version to make available on the path 70 | flutter-version: 2.8.1 # optional 71 | # The Flutter build release channel 72 | channel: stable # optional, default is stable 73 | - name: run tests 74 | run: | 75 | cd $GITHUB_WORKSPACE/org.purplegraphite.code; 76 | flutter test; 77 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/screens/history.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/common/routing_const.dart'; 2 | import 'package:code/src/models/provider/history.dart'; 3 | import 'package:code/src/ui/screens/editor/controller.dart'; 4 | import 'package:code/src/utils/theme.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'package:provider/provider.dart'; 8 | 9 | class HistoryScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | final _provider = Provider.of(context); 13 | final _histories = _provider.getHistories(); 14 | 15 | final _isDarkMode = isDarkTheme(context); 16 | final _theme = Theme.of(context); 17 | 18 | final backgroundInDark = _isDarkMode ? Colors.black : Colors.white; 19 | 20 | final popupIconButtonColor = Color.lerp(_theme.accentColor, 21 | _isDarkMode ? Colors.white : Colors.black, _isDarkMode ? 0.10 : 0.25); 22 | final foregroundColorOnDarkBackground = 23 | _isDarkMode ? Colors.white.withOpacity(1) : Color(0xEE212121); 24 | 25 | final physics = 26 | BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()); 27 | 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: Text('History'), 31 | ), 32 | body: ListView.builder( 33 | itemCount: _histories?.length == 0 ? 1 : _histories.length, 34 | physics: physics, 35 | itemBuilder: (context, index) { 36 | if (_histories?.isEmpty ?? true) { 37 | return Center( 38 | child: Padding( 39 | padding: const EdgeInsets.symmetric(vertical: 30.0), 40 | child: Text( 41 | 'You have no work history', 42 | style: TextStyle( 43 | color: foregroundColorOnDarkBackground, 44 | ), 45 | textAlign: TextAlign.center, 46 | ), 47 | ), 48 | ); 49 | } 50 | final _history = _histories[index]; 51 | return ListTile( 52 | title: Text( 53 | _history.workspacePath, 54 | style: TextStyle( 55 | color: foregroundColorOnDarkBackground, 56 | ), 57 | textAlign: TextAlign.center, 58 | ), 59 | onTap: () async { 60 | final settings = 61 | EditorSettings.fromDirectory(_history.workspacePath); 62 | await Provider.of(context, listen: false) 63 | .updateSettings(settings); 64 | Navigator.of(context).pushNamedAndRemoveUntil( 65 | EditorScreenRoute, (Route route) => false); 66 | }, 67 | // Add a fuzzy timestamp 68 | // trailing: Text(_history.lastModified.), 69 | ); 70 | }, 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /org.purplegraphite.code/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 18 | 25 | 29 | 33 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/screens/terminal.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/models/provider/theme.dart'; 2 | import 'package:code/src/models/view_model/terminal_controller.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | class TerminalScreen extends StatefulWidget { 8 | @override 9 | _TerminalScreenState createState() => _TerminalScreenState(); 10 | } 11 | 12 | class _TerminalScreenState extends State { 13 | TerminalController controller; 14 | TextEditingController _commandController; 15 | @override 16 | void initState() { 17 | // TODO: implement initState 18 | super.initState(); 19 | _commandController = TextEditingController(); 20 | Provider.of(context, listen: false).start(); 21 | } 22 | 23 | @override 24 | void didChangeDependencies() { 25 | super.didChangeDependencies(); 26 | controller = Provider.of(context); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | TextStyle consoleText = ThemeProvider.monospaceTextStyle.merge( 32 | TextStyle(color: Colors.white), 33 | ); 34 | 35 | return Scaffold( 36 | backgroundColor: Colors.grey[850], 37 | appBar: AppBar( 38 | title: Text('Terminal'), 39 | ), 40 | body: Column( 41 | children: [ 42 | Expanded( 43 | // fit: FlexFit.loose, 44 | child: Padding( 45 | padding: const EdgeInsets.only(left: 4), 46 | // To make child scrollable 47 | child: SingleChildScrollView( 48 | scrollDirection: Axis.horizontal, 49 | physics: const ClampingScrollPhysics(), 50 | child: ConstrainedBox( 51 | constraints: const BoxConstraints.expand(width: 1500), 52 | child: ListView.builder( 53 | reverse: true, 54 | shrinkWrap: true, 55 | itemCount: controller.outputs.length, 56 | itemBuilder: (context, i) { 57 | var result = 58 | controller.outputs[controller.outputs.length - 1 - i]; 59 | return Text( 60 | result.toString(), 61 | style: consoleText, 62 | ); 63 | }, 64 | ), 65 | ), 66 | ), 67 | ), 68 | ), 69 | TextField( 70 | controller: _commandController, 71 | keyboardType: TextInputType.text, 72 | textInputAction: TextInputAction.done, 73 | style: consoleText, 74 | decoration: InputDecoration( 75 | hintText: 'Enter commands here', 76 | hintStyle: consoleText.apply(color: Colors.white70)), 77 | onSubmitted: (_) async { 78 | await controller.execute(_); 79 | _commandController.clear(); 80 | }, 81 | ), 82 | ], 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/models/view_model/terminal_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/widgets.dart'; 5 | 6 | class ConsoleOutput { 7 | final String command; 8 | List _outList = []; 9 | 10 | List get outputs => _outList; 11 | 12 | List _errList = []; 13 | 14 | List get errors => _errList; 15 | 16 | Map environment = {}; 17 | ConsoleOutput(this.command); 18 | void addLog(Object object, {bool isError: false}) { 19 | if (isError) { 20 | _errList.add(object.toString()); 21 | } else { 22 | _outList.add(object.toString()); 23 | } 24 | } 25 | 26 | void updateEnv(Map env) { 27 | environment = env; 28 | } 29 | 30 | String toString() { 31 | return '${errors.join()}\n${outputs.join()}'; 32 | } 33 | } 34 | 35 | class TerminalController extends ChangeNotifier { 36 | Map environment = {}; 37 | // List _cache = []; 38 | List _outList = []; 39 | 40 | ConsoleOutput get lastConsoleLog { 41 | if (_outList.isEmpty) { 42 | return ConsoleOutput(''); 43 | } 44 | return _outList.last; 45 | } 46 | 47 | List get outputs => _outList; 48 | final String prefix = '/data/data/org.basil.code'; 49 | IOSink ios; 50 | Process _mainProcess; 51 | bool initialized = false; 52 | void ensureInitialized() { 53 | while (!initialized); 54 | } 55 | 56 | void start() async { 57 | var co = ConsoleOutput('sh -lva'); 58 | _outList.add(co); 59 | _mainProcess = await Process.start( 60 | '/bin/sh', 61 | ['-ilva'], 62 | runInShell: true, 63 | environment: environment, 64 | ).then((result) { 65 | result.stdout.listen(comprehendStdout); 66 | result.stderr.listen(comprehendStderr); 67 | // result.stdin.addStream(result.stdout); 68 | return result; 69 | }); 70 | ios = _mainProcess.stdin; 71 | initialized = true; 72 | co.updateEnv(environment); 73 | debug(); 74 | notifyListeners(); 75 | execute('cd $prefix'); 76 | execute('clear'); 77 | } 78 | 79 | void execute(String command) { 80 | if (command.contains('clear')) { 81 | // _cache = _outList; 82 | _outList = []; 83 | notifyListeners(); 84 | return; 85 | } 86 | ensureInitialized(); 87 | _outList.add(ConsoleOutput(command)); 88 | ios.writeln(command); 89 | debug(); 90 | } 91 | 92 | void debug() { 93 | print('Environment ${environment.keys.toList().join('\n')}'); 94 | // print(_outList); 95 | } 96 | 97 | String decodeData(List codeUnits) { 98 | // final lines = utf8.decoder.convert(codeUnits); 99 | // .bind(File(path).openRead()) 100 | // .transform(const LineSplitter()); 101 | return utf8.decode(codeUnits); 102 | } 103 | 104 | void comprehendStdout(List data) { 105 | lastConsoleLog.addLog(decodeData(data)); 106 | lastConsoleLog.updateEnv(environment); 107 | notifyListeners(); 108 | } 109 | 110 | void comprehendStderr(List error) { 111 | lastConsoleLog.addLog(decodeData(error), isError: true); 112 | lastConsoleLog.updateEnv(environment); 113 | notifyListeners(); 114 | } 115 | 116 | void clean() { 117 | ensureInitialized(); 118 | ios.flush(); 119 | ios.close(); 120 | _mainProcess.kill(); 121 | } 122 | 123 | @override 124 | void dispose() { 125 | super.dispose(); 126 | clean(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at smushaheed@outlook.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/ask_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class _Consts { 4 | _Consts._(); 5 | 6 | static const double padding = 16.0; 7 | static const double avatarRadius = 66.0; 8 | } 9 | 10 | Future showMessage(BuildContext context) { 11 | return showDialog( 12 | context: context, 13 | builder: (BuildContext context) => CustomDialog( 14 | title: "Success", 15 | description: 16 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 17 | buttonText: "Okay", 18 | ), 19 | ); 20 | } 21 | 22 | class CustomDialog extends StatelessWidget { 23 | final String title, description, buttonText; 24 | final Image image; 25 | 26 | Widget dialogContent(BuildContext context) { 27 | return Stack( 28 | children: [ 29 | //...bottom card part, 30 | Container( 31 | padding: EdgeInsets.only( 32 | top: _Consts.avatarRadius + _Consts.padding, 33 | bottom: _Consts.padding, 34 | left: _Consts.padding, 35 | right: _Consts.padding, 36 | ), 37 | margin: EdgeInsets.only(top: _Consts.avatarRadius), 38 | decoration: new BoxDecoration( 39 | color: Colors.white, 40 | shape: BoxShape.rectangle, 41 | borderRadius: BorderRadius.circular(_Consts.padding), 42 | boxShadow: [ 43 | BoxShadow( 44 | color: Colors.black26, 45 | blurRadius: 10.0, 46 | offset: const Offset(0.0, 10.0), 47 | ), 48 | ], 49 | ), 50 | child: Column( 51 | mainAxisSize: MainAxisSize.min, // To make the card compact 52 | children: [ 53 | Text( 54 | title, 55 | style: TextStyle( 56 | fontSize: 24.0, 57 | fontWeight: FontWeight.w700, 58 | ), 59 | ), 60 | SizedBox(height: 16.0), 61 | Text( 62 | description, 63 | textAlign: TextAlign.center, 64 | style: TextStyle( 65 | fontSize: 16.0, 66 | ), 67 | ), 68 | SizedBox(height: 24.0), 69 | Align( 70 | alignment: Alignment.bottomRight, 71 | child: FlatButton( 72 | onPressed: () { 73 | Navigator.of(context).pop(); // To close the dialog 74 | }, 75 | child: Text(buttonText), 76 | ), 77 | ), 78 | ], 79 | ), 80 | ), 81 | //...top circlular image part, 82 | Positioned( 83 | left: _Consts.padding, 84 | right: _Consts.padding, 85 | child: CircleAvatar( 86 | backgroundColor: Colors.blueAccent, 87 | radius: _Consts.avatarRadius, 88 | ), 89 | ), 90 | ], 91 | ); 92 | } 93 | 94 | CustomDialog({ 95 | @required this.title, 96 | @required this.description, 97 | @required this.buttonText, 98 | this.image, 99 | }); 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | return Dialog( 104 | shape: RoundedRectangleBorder( 105 | borderRadius: BorderRadius.circular(_Consts.padding), 106 | ), 107 | elevation: 0.0, 108 | backgroundColor: Colors.transparent, 109 | child: dialogContent(context), 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/syntax_highlighter/language_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | /// Language type which the syntax highlighting parser will use. 4 | enum LanguageType { 5 | /// This is actually 1C prgamming language. [LanguageType.language_1c] is 6 | /// just an alias of 1C language in this package __only__. 7 | language_1c, 8 | abnf, 9 | accesslog, 10 | actionscript, 11 | ada, 12 | all, 13 | angelscript, 14 | apache, 15 | applescript, 16 | arcade, 17 | arduino, 18 | armasm, 19 | asciidoc, 20 | aspectj, 21 | autohotkey, 22 | autoit, 23 | avrasm, 24 | awk, 25 | axapta, 26 | bash, 27 | basic, 28 | bnf, 29 | brainfuck, 30 | cal, 31 | capnproto, 32 | ceylon, 33 | clean, 34 | clojure, 35 | clojure_repl, 36 | cmake, 37 | coffeescript, 38 | coq, 39 | cos, 40 | cpp, 41 | crmsh, 42 | crystal, 43 | cs, 44 | csp, 45 | css, 46 | dart, 47 | d, 48 | delphi, 49 | diff, 50 | django, 51 | dns, 52 | dockerfile, 53 | dos, 54 | dsconfig, 55 | dts, 56 | dust, 57 | ebnf, 58 | elixir, 59 | elm, 60 | erb, 61 | erlang, 62 | erlang_repl, 63 | excel, 64 | fix, 65 | flix, 66 | fortran, 67 | fsharp, 68 | gams, 69 | gauss, 70 | gcode, 71 | gherkin, 72 | glsl, 73 | gml, 74 | gn, 75 | go, 76 | golo, 77 | gradle, 78 | graphql, 79 | groovy, 80 | haml, 81 | handlebars, 82 | haskell, 83 | haxe, 84 | hsp, 85 | htmlbars, 86 | http, 87 | hy, 88 | inform7, 89 | ini, 90 | irpf90, 91 | isbl, 92 | java, 93 | javascript, 94 | jboss_cli, 95 | json, 96 | julia, 97 | julia_repl, 98 | kotlin, 99 | lasso, 100 | ldif, 101 | leaf, 102 | less, 103 | lisp, 104 | livecodeserver, 105 | livescript, 106 | llvm, 107 | lsl, 108 | lua, 109 | makefile, 110 | markdown, 111 | mathematica, 112 | matlab, 113 | maxima, 114 | mel, 115 | mercury, 116 | mipsasm, 117 | mizar, 118 | mojolicious, 119 | monkey, 120 | moonscript, 121 | n1ql, 122 | nginx, 123 | nimrod, 124 | nix, 125 | nsis, 126 | objectivec, 127 | ocaml, 128 | openscad, 129 | oxygene, 130 | parser3, 131 | perl, 132 | pf, 133 | pgsql, 134 | php, 135 | plaintext, 136 | pony, 137 | powershell, 138 | processing, 139 | profile, 140 | prolog, 141 | properties, 142 | protobuf, 143 | puppet, 144 | purebasic, 145 | python, 146 | q, 147 | qml, 148 | r, 149 | reasonml, 150 | rib, 151 | roboconf, 152 | routeros, 153 | rsl, 154 | ruby, 155 | ruleslanguage, 156 | rust, 157 | sas, 158 | scala, 159 | scheme, 160 | scilab, 161 | scss, 162 | shell, 163 | smali, 164 | smalltalk, 165 | sml, 166 | solidity, 167 | sqf, 168 | sql, 169 | stan, 170 | stata, 171 | step21, 172 | stylus, 173 | subunit, 174 | swift, 175 | taggerscript, 176 | tap, 177 | tcl, 178 | tex, 179 | thrift, 180 | tp, 181 | twig, 182 | typescript, 183 | vala, 184 | vbnet, 185 | vbscript, 186 | vbscript_html, 187 | verilog, 188 | vhdl, 189 | vim, 190 | vue, 191 | x86asm, 192 | xl, 193 | xml, 194 | xquery, 195 | yaml, 196 | zephir, 197 | } 198 | 199 | /// Describes the language name. 200 | /// 201 | /// Strips off the enum class name from the `LanguageType.toString()` and 202 | /// returns a proper name for the syntax highlighter's parser. 203 | String toLanguageName(LanguageType enumEntry) { 204 | final String language = describeEnum(enumEntry); 205 | 206 | // handle exceptionals 207 | if (language.isEmpty) return 'all'; 208 | 209 | switch (language) { 210 | case "language_1c": 211 | return "1c"; 212 | default: 213 | return language.replaceAll('_', '-'); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/common/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/ui/components/start_tips/tips.dart'; 2 | import 'package:code/src/ui/screens/editor/editor.dart'; 3 | import 'package:code/src/ui/screens/history.dart'; 4 | import 'package:code/src/ui/screens/start/controller.dart'; 5 | import 'package:code/src/ui/screens/start/start.dart'; 6 | import 'package:code/src/ui/screens/workspace_explorer.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | // Constant String screen's path address 12 | import '../common/routing_const.dart'; 13 | 14 | // Screens 15 | import '../../main.dart'; 16 | import '../ui/screens/browser.dart'; 17 | import '../ui/screens/settings.dart'; 18 | import '../ui/screens/terminal.dart'; 19 | 20 | // Models 21 | import '../models/view_model/browser_controller.dart'; 22 | import '../models/view_model/terminal_controller.dart'; 23 | 24 | /// Wraps [screen] with a [PageRoute] 25 | PageRoute wrapPageRoute(Widget screen, 26 | [bool useCupertinoPageRoute = true]) { 27 | if (useCupertinoPageRoute) { 28 | return CupertinoPageRoute(builder: (context) => screen); 29 | } 30 | return MaterialPageRoute( 31 | builder: (context) => screen, 32 | ); 33 | } 34 | 35 | /// Generates Routes which will be used in the application 36 | Route generateRoute(RouteSettings settings) { 37 | switch (settings.name) { 38 | case StartScreenRoute: 39 | return wrapPageRoute( 40 | MultiProvider( 41 | providers: [ 42 | ChangeNotifierProvider( 43 | create: (context) => StartTipsController(), 44 | ), 45 | ChangeNotifierProvider( 46 | create: (context) => StartScreenController(), 47 | ), 48 | ], 49 | child: StartScreen(), 50 | ), 51 | ); 52 | case EditorScreenRoute: 53 | return wrapPageRoute(EditorScreen()); 54 | case BrowserScreenRoute: 55 | return wrapPageRoute( 56 | ChangeNotifierProvider( 57 | create: (context) => BrowserController(), 58 | child: BrowserScreen( 59 | dir: settings.arguments, 60 | ), 61 | ), 62 | ); 63 | case SettingsScreenRoute: 64 | return wrapPageRoute(SettingsScreen()); 65 | case WorkspaceExplorerScreenRoute: 66 | return wrapPageRoute( 67 | ChangeNotifierProvider( 68 | create: (context) => BrowserController(), 69 | child: WorkspaceExplorerScreen(dir: settings.arguments), 70 | ), 71 | ); 72 | case TerminalScreenRoute: 73 | return wrapPageRoute( 74 | ChangeNotifierProvider( 75 | create: (context) => TerminalController(), 76 | child: TerminalScreen(), 77 | ), 78 | ); 79 | case HistoryScreenRoute: 80 | return wrapPageRoute(HistoryScreen()); 81 | case RootRoute: 82 | default: 83 | // TODO(mushaheedx): Reroute from Root to Start instead of replacing a widget in Root with Start 84 | return wrapPageRoute( 85 | MultiProvider( 86 | providers: [ 87 | ChangeNotifierProvider( 88 | create: (context) => StartTipsController(), 89 | ), 90 | ChangeNotifierProvider( 91 | create: (context) => StartScreenController(), 92 | ), 93 | ], 94 | child: Root( 95 | key: RootRouteKey, 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snake Code (org.purplegraphite.code) 2 | 3 | _Effortlessly programming_ on mobile 4 | 5 | [![Flutter analysis](https://github.com/predatorx7/snake_code/workflows/Flutter%20analysis/badge.svg)](https://github.com/predatorx7/snake_code/actions?query=workflow%3A%22Flutter+analysis%22) 6 | 7 | Snake Code is just a simple source code editor. This project's main target platform is Android. 8 | 9 | The application is written in dart (uses Flutter UI Toolkit). 10 | 11 | ## Objective 12 | 13 | - An easy to use source code editor 14 | - Open directories/files as projects 15 | - Syntax highlighting 16 | - Basic editing tools for ex. Find & replace 17 | 18 | --- 19 | 20 | ## Structure 21 | 22 | ```tree 23 | ├── org.purplegraphite.code 24 | └── packages 25 | └── creamy_field 26 | ``` 27 | 28 | ### org.purplegraphite.code 29 | 30 | The main flutter project code resides here. 31 | 32 | ### _packages_ 33 | 34 | All the internal and general purpose packages used in org.purplegraphite.code is here. 35 | 36 | - creamy_field 37 | 38 | The package provides components & widgets with rich text, custom selection toolbar & syntax highlight support. 39 | 40 | --- 41 | 42 | ## A brief information about the project 43 | 44 | ### Problem this tries to solves 45 | 46 | This project aims to help make programming on mobile devices easier. 47 | 48 | It will focus on being flexible in functionality by providing various in-app settings for users and allowing support for extensions in future. 49 | Extensions built by us or others can improve suggestions, performance, theming, etc. 50 | 51 | It helps in modifying a source code of a project simple by allowing to edit multiple files at once in tabs and switching between them quicker in a nice interface. 52 | 53 | It'll include a file directory explorer for faster exploration of files. Simple to use yet powerful search, replace, and other tools to aid in writing. 54 | 55 | ### Some functionalities 56 | 57 | 1. A project explorer to help browsing files a user needs faster and less messy. Existing similar applications show all files in a project as an expandable tile which makes the list too long & wide making it difficult to distinguish between files of one folder with another folder. 58 | 1. A search tool to globally search for files with names, or files with a text equal or similar to the searched query. 59 | 1. A tab switcher to change between current tabs on screen quickly similar to a web browser. 60 | 1. Flexibility in functionality with a lot of options in settings and support for extensions to change themes, syntax highlighting, way of debugging. The app will also be flexible in importing a project and exporting/sharing it in a portable but fast format. 61 | 62 | ### Users 63 | 64 | The type of users which may use this app. 65 | 66 | 1. Users who don't have money to buy a computer for an indefinite amount of time but have a mobile device available. 67 | 1. Programmers who need to edit a project on a mobile device because they are commuting in a transport or due to temporary unavailability of a computer. 68 | 1. Users who just want to quickly run and share a small program with each other in groups. 69 | 1. Users who don’t want to waste money on buying a computer if the app is able to run projects on mobile. 70 | 71 | Users (likely students) from an economically weak background wouldn’t be able to afford a full fledged computer but could have access to a mobile. These users can use this app and try to achieve relevant skills by learning and developing programs. My app will try to not let an unavailability of a computer for an indefinite amount of time be an obstacle for these users. 72 | 73 | ### Developer goals for this app's design 74 | 75 | 1. Build community for extension development for making this app more functionally flexible and vast. 76 | 1. Keep this app as an economically cheap alternative for program development for users. 77 | 1. A medium to quickly run code and share it with other users. 78 | 1. Fast in its functions to improve user’s productivity. 79 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/screens/editor_tab.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:code/src/models/plain_model/entity.dart'; 4 | import 'package:code/src/models/provider/theme.dart'; 5 | import 'package:creamy_field/creamy_field.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class EditorTabController with ChangeNotifier { 9 | String get directoryPath => file.path; 10 | 11 | final Entity entity; 12 | File get file => entity.entity as File; 13 | 14 | ScrollController scrollController; 15 | FocusNode focusNode; 16 | CreamyEditingController textController; 17 | 18 | bool _initialized = false; 19 | bool get isInitialized => _initialized; 20 | 21 | void initState() { 22 | scrollController = ScrollController(); 23 | textController = CreamyEditingController(); 24 | focusNode = FocusNode(); 25 | _initialized = true; 26 | } 27 | 28 | @override 29 | void dispose() { 30 | scrollController.dispose(); 31 | focusNode.dispose(); 32 | textController.dispose(); 33 | super.dispose(); 34 | _initialized = false; 35 | } 36 | 37 | EditorTabController(this.entity); 38 | } 39 | 40 | class EditorTab extends StatefulWidget with Comparable { 41 | final EditorTabController controller; 42 | 43 | const EditorTab._({ 44 | @required this.controller, 45 | Key key, 46 | }) : super(key: key); 47 | 48 | factory EditorTab.fromFile(File file) { 49 | return EditorTab._( 50 | controller: EditorTabController(Entity(file)), 51 | ); 52 | } 53 | 54 | @override 55 | int compareTo(EditorTab other) { 56 | return this.controller.entity.compareTo(other.controller.entity); 57 | } 58 | 59 | @override 60 | _EditorTabState createState() => 61 | _EditorTabState(ValueKey(controller.directoryPath)); 62 | } 63 | 64 | class _EditorTabState extends State { 65 | final ValueKey key; 66 | 67 | _EditorTabState(this.key); 68 | 69 | @override 70 | void initState() { 71 | widget.controller.initState(); 72 | super.initState(); 73 | } 74 | 75 | @override 76 | void dispose() { 77 | widget.controller.dispose(); 78 | super.dispose(); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | final foregroundInDark = (Theme.of(context).brightness == Brightness.dark) 84 | ? Colors.white 85 | : Colors.black; 86 | return LineCountIndicator( 87 | textControllerOfTextField: widget.controller.textController, 88 | scrollControllerOfTextField: widget.controller.scrollController, 89 | decoration: LineCountIndicatorDecoration( 90 | textStyle: ThemeProvider.monospaceTextStyle, 91 | ), 92 | child: HorizontalScrollable( 93 | child: TextField( 94 | key: key, 95 | controller: widget.controller.textController, 96 | keyboardType: TextInputType.multiline, 97 | textInputAction: TextInputAction.newline, 98 | textCapitalization: TextCapitalization.none, 99 | textAlign: TextAlign.left, 100 | textDirection: TextDirection.ltr, 101 | obscureText: false, 102 | focusNode: widget.controller.focusNode, 103 | style: ThemeProvider.monospaceTextStyle 104 | .copyWith(color: foregroundInDark), 105 | autocorrect: true, 106 | enableSuggestions: true, 107 | maxLines: null, 108 | scrollPadding: const EdgeInsets.all(20.0), 109 | smartDashesType: SmartDashesType.enabled, 110 | smartQuotesType: SmartQuotesType.enabled, 111 | textAlignVertical: TextAlignVertical.top, 112 | scrollController: widget.controller.scrollController, 113 | decoration: InputDecoration.collapsed( 114 | hintText: 'Start writing..', 115 | hintStyle: ThemeProvider.monospaceTextStyle.copyWith( 116 | color: foregroundInDark.withOpacity(0.5), 117 | ), 118 | ), 119 | ), 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/newfolder_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class _Consts { 5 | _Consts._(); 6 | 7 | static const double padding = 16.0; 8 | static const double avatarRadius = 40.0; 9 | } 10 | 11 | Future newFolderDialog( 12 | BuildContext context, 13 | void Function(TextEditingController controller) onButtonPress, [ 14 | bool file = false, 15 | ]) { 16 | return showDialog( 17 | context: context, 18 | builder: (BuildContext context) => CustomDialog( 19 | title: "Enter ${file ? 'file' : 'folder'} name", 20 | buttonText: "Create", 21 | onPressed: onButtonPress, 22 | ), 23 | ); 24 | } 25 | 26 | class CustomDialog extends StatelessWidget { 27 | final String title, buttonText; 28 | final Image image; 29 | final void Function(TextEditingController controller) onPressed; 30 | final TextEditingController controller = TextEditingController(); 31 | Widget dialogContent(BuildContext context) { 32 | return Stack( 33 | children: [ 34 | //...bottom card part, 35 | Container( 36 | padding: EdgeInsets.only( 37 | top: _Consts.avatarRadius + _Consts.padding, 38 | bottom: _Consts.padding, 39 | left: _Consts.padding, 40 | right: _Consts.padding, 41 | ), 42 | margin: EdgeInsets.only(top: _Consts.avatarRadius), 43 | decoration: new BoxDecoration( 44 | color: Colors.white, 45 | shape: BoxShape.rectangle, 46 | borderRadius: BorderRadius.circular(_Consts.padding), 47 | boxShadow: [ 48 | BoxShadow( 49 | color: Colors.black26, 50 | blurRadius: 10.0, 51 | offset: const Offset(0.0, 10.0), 52 | ), 53 | ], 54 | ), 55 | child: Column( 56 | mainAxisSize: MainAxisSize.min, // To make the card compact 57 | children: [ 58 | Text( 59 | title, 60 | style: TextStyle( 61 | fontSize: 18.0, 62 | fontWeight: FontWeight.w700, 63 | ), 64 | ), 65 | SizedBox(height: 16.0), 66 | TextField( 67 | controller: controller, 68 | textAlign: TextAlign.center, 69 | style: TextStyle( 70 | fontSize: 16.0, 71 | ), 72 | decoration: InputDecoration(), 73 | ), 74 | SizedBox(height: 24.0), 75 | Align( 76 | alignment: Alignment.bottomRight, 77 | child: FlatButton( 78 | onPressed: () { 79 | onPressed(controller); 80 | Navigator.of(context).pop(); // To close the dialog 81 | }, 82 | child: Text(buttonText), 83 | ), 84 | ), 85 | ], 86 | ), 87 | ), 88 | //...top circlular image part, 89 | Positioned( 90 | left: _Consts.padding, 91 | right: _Consts.padding, 92 | child: CircleAvatar( 93 | backgroundColor: Theme.of(context).accentColor, 94 | radius: _Consts.avatarRadius, 95 | child: Icon( 96 | FluentIcons.folder_add_28_regular, 97 | size: 28, 98 | ), 99 | ), 100 | ), 101 | ], 102 | ); 103 | } 104 | 105 | CustomDialog({ 106 | @required this.title, 107 | @required this.onPressed, 108 | @required this.buttonText, 109 | this.image, 110 | }); 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | return Dialog( 115 | shape: RoundedRectangleBorder( 116 | borderRadius: BorderRadius.circular(_Consts.padding), 117 | ), 118 | elevation: 0.0, 119 | backgroundColor: Colors.transparent, 120 | child: dialogContent(context), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/models/provider/history.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'src/common/routes.dart'; 6 | import 'src/models/provider/theme.dart'; 7 | import 'src/ui/screens/editor/controller.dart'; 8 | import 'src/ui/screens/start/start.dart'; 9 | import 'src/utils/permissions.dart'; 10 | 11 | void main() async { 12 | runApp(App()); 13 | } 14 | 15 | /// Starting point for our app 16 | class App extends StatelessWidget { 17 | // Usually, without keys widgets unmounts and then mounts again and rebuilds. 18 | // This behaviour may creates performance issues. 19 | // Thus, using keys below may cause widgets to update instead of remounting. 20 | // Using keys isn't neccessary as Flutter is fast but we don't want to remount 21 | // heavy widgets hence the usage 22 | final _appKey = GlobalKey(); 23 | final GlobalKey _navigatorKey = GlobalKey(); 24 | 25 | final _recents = RecentHistoryProvider(); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | final _themeProvider = ThemeProvider( 30 | navigatorKey: _navigatorKey, 31 | ); 32 | 33 | // The [MultiProvider] builds providers which instances of object throughout 34 | // the widget tree with a search complexity of O(1) 35 | return MultiProvider( 36 | providers: [ 37 | // Provides instance of class initiated at the create parameter 38 | ChangeNotifierProvider.value( 39 | // Provides theme to the descendant widgets. 40 | // Use Provider.of(context) to get it's instance. 41 | value: _themeProvider, 42 | ), 43 | ChangeNotifierProvider( 44 | create: (_) => EditorController(_recents, _themeProvider), 45 | ), 46 | ChangeNotifierProvider( 47 | create: (_) => _recents, 48 | ) 49 | ], 50 | child: Consumer( 51 | builder: (context, th, child) { 52 | return MaterialApp( 53 | key: _appKey, 54 | 55 | /// Will be used to catch intents, and to hanle Routes 56 | /// without context 57 | navigatorKey: _navigatorKey, 58 | title: 'Snake editor', 59 | theme: th.currentLightTheme, 60 | darkTheme: th.currentDarkTheme, 61 | themeMode: th.themeMode, 62 | // Don't need to explicitly specify home as [generateRoute] does it. 63 | onGenerateRoute: generateRoute, 64 | ); 65 | }, 66 | ), 67 | ); 68 | } 69 | } 70 | 71 | /// The main root widget route which will ask permissions and show notifications 72 | /// on load 73 | class Root extends StatefulWidget { 74 | /// This root will show notifications and check storage access permissions. 75 | /// If the permissions have been aapproved, bg widget will replace itself 76 | /// with [StartScreen] 77 | const Root({Key key}) : super(key: key); 78 | @override 79 | _RootState createState() => _RootState(); 80 | } 81 | 82 | class _RootState extends State { 83 | final ValueKey _mainKey = const ValueKey('mainScreen'); 84 | bool perms = false; 85 | 86 | /// Ask permissions & then change [perms] to true 87 | Future _requestPermissions() async { 88 | // Show dialog here before requesting permissions. 89 | await Perms.askOnce(); 90 | setState(() { 91 | perms = true; 92 | }); 93 | } 94 | 95 | void initState() { 96 | super.initState(); 97 | WidgetsBinding.instance.addPostFrameCallback((_) => _requestPermissions()); 98 | } 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | Widget start = StartScreen( 103 | key: _mainKey, 104 | ); 105 | 106 | // TODO(predatorx7): Show a dialog to describe why you need permissions. 107 | return Visibility( 108 | visible: perms, 109 | child: start, 110 | // Shows this widget until [perms] is true 111 | replacement: Scaffold( 112 | body: Center( 113 | child: CircularProgressIndicator(), 114 | ), 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/creamy_field/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:creamy_field/creamy_field.dart'; // imported the package 3 | 4 | void main() { 5 | final ThemeData _theme = ThemeData(primarySwatch: Colors.blue); 6 | runApp(MaterialApp( 7 | home: MyEditorApp(), 8 | // Change theme mode to try syntax highlighting colors in dark mode & light mode 9 | themeMode: ThemeMode.light, 10 | theme: ThemeData(primarySwatch: Colors.blue), 11 | darkTheme: _theme.copyWith(brightness: Brightness.dark), 12 | )); 13 | } 14 | 15 | class MyEditorApp extends StatefulWidget { 16 | @override 17 | _MyEditorAppState createState() => _MyEditorAppState(); 18 | } 19 | 20 | class _MyEditorAppState extends State { 21 | // Declared a regular syntax controller. 22 | late CreamyEditingController controller; 23 | late ScrollController scrollController; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | 29 | // The below example shows [CreamyEditingController], a text editing controller with RichText highlighting support 30 | controller = CreamyEditingController( 31 | // This is the CreamySyntaxHighlighter which will be used by the controller 32 | // to generate list of RichText for syntax highlighting 33 | syntaxHighlighter: CreamySyntaxHighlighter( 34 | language: LanguageType.dart, 35 | theme: HighlightedThemeType.defaultTheme, 36 | ), 37 | // The number of spaces which will replace `\t`. 38 | // Setting this to 1 does nothing & setting this to value less than 1 39 | // throws assertion error. 40 | tabSize: 4, 41 | ); 42 | scrollController = ScrollController(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | controller.dispose(); 48 | scrollController.dispose(); 49 | super.dispose(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | bool _isDark = Theme.of(context).brightness == Brightness.dark; 55 | return new Scaffold( 56 | backgroundColor: _isDark ? Colors.black : Colors.white, 57 | appBar: new AppBar( 58 | title: new Text("Rich Code Editor"), 59 | actions: [ 60 | TextButton( 61 | child: Text('Add tab'), 62 | onPressed: () { 63 | // Adds a tab at the selection's base base-offet 64 | controller.addTab(); 65 | }, 66 | ) 67 | ], 68 | ), 69 | // Shows line indicator column adjacent to this widget 70 | body: LineCountIndicator( 71 | textControllerOfTextField: controller, 72 | scrollControllerOfTextField: scrollController, 73 | decoration: LineCountIndicatorDecoration( 74 | backgroundColor: Colors.grey, 75 | ), 76 | // Allow this Text field to be horizontally scrollable 77 | child: HorizontalScrollable( 78 | // Additional options for text selection widget 79 | child: TextField( 80 | autofocus: true, 81 | // Our controller should be up casted as CreamyEditingController 82 | // Note: Declare controller as CreamyEditingController if this fails. 83 | controller: controller, 84 | scrollController: scrollController, 85 | textCapitalization: TextCapitalization.none, 86 | decoration: InputDecoration.collapsed(hintText: 'Start writing'), 87 | maxLines: null, 88 | selectionControls: CreamyTextSelectionControlsProvider( 89 | type: TextSelectionControlsType.material, 90 | actionsBuilder: (_, __, ___) { 91 | return [ 92 | CreamyTextSelectionToolbarAction( 93 | label: 'Button1', 94 | onPressed: () { 95 | print('Button2'); 96 | }, 97 | ), 98 | CreamyTextSelectionToolbarAction( 99 | label: 'Button2', 100 | onPressed: () { 101 | print('Button2'); 102 | }, 103 | ), 104 | ]; 105 | }, 106 | ), 107 | ), 108 | ), 109 | ), 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /packages/creamy_field/README.md: -------------------------------------------------------------------------------- 1 | # creamy_field 2 | 3 | [![](https://img.shields.io/pub/v/creamy_field)](https://pub.dev/packages/creamy_field) 4 | [![](https://img.shields.io/github/issues/predatorx7/snake_code)](https://github.com/predatorx7/snake_code/issues) 5 | [![Flutter package analysis](https://github.com/predatorx7/snake_code/workflows/Flutter%20package%20analysis/badge.svg?branch=packages%2Fcreamy_field)](https://github.com/predatorx7/snake_code/actions?query=workflow%3A%22Flutter+package+analysis%22) 6 | 7 | Components & widgets with rich text, custom selection toolbar & syntax highlight support. Useful for Rich text editors. 8 | 9 | ## Getting Started 10 | 11 | ### The main components of this package 12 | 13 | 1. [CreamyTextField (REMOVED)](#CreamyTextField) 14 | 1. [CreamyEditingController](#CreamyEditingController) 15 | 1. [Syntax Highlighter](#CreamySyntaxHighlighter) 16 | 1. [LineCountIndicator](#LineCountIndicator) 17 | 1. [HorizontalScrollable](#HorizontalScrollable) 18 | 1. [CreamyTextSelectionControlsProvider](#CreamyTextSelectionControlsProvider) 19 | 20 | The creamy_field package uses some code from flutter to keep API similar/compatible with other components in flutter. 21 | 22 | #### CreamyTextField 23 | 24 | This widget has been removed. 25 | 26 | The CreamyTextField was a text widget similar to Flutter's `TextField` widget with additional features. However, with update to flutter v2.x.x, we don't need this anymore. Components from this package can be used with flutter library to provide additional functionality while editing text. 27 | 28 | #### CreamySyntaxHighlighter 29 | 30 | You can use a limited support for syntax highlighting of many programming languages & themes using CreamySyntaxHighlighter. 31 | 32 | Since, the text field itself is independent of the syntax highlighting rules, you will only need to implement the syntax highlighter implementation separately for your custom syntax and provide this to the controller. 33 | 34 | #### CreamyEditingController 35 | 36 | The CreamyEditingController is responsible for changing tab sizes, applyng syntax highlighting to text and providing other useful information like line count. 37 | 38 | You can use CreamyEditingController as TextEditingController in regular TextFields/TextFormFields of flutter. Syntax highlighting will work on them too. 39 | 40 | Some text features described above are provided by an extension on TextEditingController. Check [CreamyTextFieldExtensions]. 41 | 42 | #### LineCountIndicator 43 | 44 | A horizontal widget with lists of indexes to represent adjacent TextField's line number. 45 | 46 | A [LineCountIndicatorDecoration] decoration can be applied to this widget. 47 | 48 | Make sure that the decoration uses the same font-family & font-size from the TextField. 49 | 50 | #### HorizontalScrollable 51 | 52 | Makes a child widget horizontally scrollable. Developed with intent of wrapping a TextField to make it horizontally scrollable. 53 | 54 | In the future, the controller in this widget which is provided to a child TextField will be used to determine horizontal scroll extent. 55 | 56 | #### CreamyTextSelectionControlsProvider 57 | 58 | The class can be used to add additional toolbar actions to a selection toolbar in a text field. 59 | 60 | This package provides several text selection controls (only 1 in v0.4.0) via CreamyTextSelectionControls. You can create your own selection controls which supports additional toolbar actions by mixing with [CreamyTextSelectionControls] and providing that in [CreamyTextSelectionControlsProvider.custom]. 61 | 62 | ### Note 63 | 64 | - Versions **before** v0.4.0 is not compatible with flutter v2.x.x due to a lot of breaking changes introduced in Text editing APIs. We'll try to keep the API stable in v0.4.0 and above. 65 | 66 | - Use [creamy_field v0.3.3](https://pub.dev/packages/creamy_field/versions/0.3.3) if you're using flutter sdk `>=1.22.0 <2.0.0` 67 | 68 | - Use [creamy_field v0.3.2](https://pub.dev/packages/creamy_field/versions/0.3.2) if you're using flutter sdk `>1.20.0 <1.22.0` 69 | 70 | - Use [creamy_field v0.3.1](https://pub.dev/packages/creamy_field/versions/0.3.1) if you're using flutter sdk `<=1.20.0` 71 | 72 | 73 | 74 | Check [screenshots folder](https://github.com/predatorx7/snake_code/tree/master/packages/creamy_field/screenshots) for some sample UI screenshots. 75 | 76 | Feel free to add features, [issues](https://github.com/predatorx7/snake_code/issues) & [pull request](https://github.com/predatorx7/snake_code/pulls) 77 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/drawer/editor_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:code/src/common/routing_const.dart'; 4 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class ColoredListTile extends StatelessWidget { 8 | final bool isDark; 9 | final MaterialColor color; 10 | final void Function() onPressed; 11 | final Widget title; 12 | final Widget leading; 13 | 14 | const ColoredListTile( 15 | this.isDark, this.color, this.onPressed, this.title, this.leading, 16 | {Key key}) 17 | : super(key: key); 18 | 19 | Widget build(BuildContext context) { 20 | return Container( 21 | color: isDark ? color[700] : color[500], 22 | child: OutlineButton( 23 | padding: EdgeInsets.zero, 24 | borderSide: BorderSide.none, 25 | splashColor: color[600], 26 | onPressed: onPressed, 27 | child: ListTile( 28 | title: title, 29 | leading: IconTheme( 30 | data: Theme.of(context) 31 | .iconTheme 32 | .copyWith(color: isDark ? Colors.white : Colors.black), 33 | child: leading, 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | class EditorDrawer extends StatelessWidget { 42 | final Directory folder; 43 | 44 | const EditorDrawer({Key key, this.folder}) : super(key: key); 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final ThemeData _theme = Theme.of(context); 49 | bool isDark = _theme.brightness == Brightness.dark; 50 | Color objectColor = isDark ? Colors.white : Colors.black; 51 | return Theme( 52 | data: Theme.of(context).copyWith( 53 | canvasColor: isDark ? Colors.black87 : null, 54 | iconTheme: Theme.of(context).iconTheme.copyWith( 55 | color: objectColor, 56 | ), 57 | textTheme: Theme.of(context).textTheme.copyWith( 58 | bodyText1: TextStyle(color: objectColor), 59 | ), 60 | ), 61 | child: Drawer( 62 | child: ListView( 63 | children: [ 64 | // ListTile( 65 | // title: const Text("Finder"), 66 | // leading: Icon(Icons.find_in_page), 67 | // ), 68 | Tooltip( 69 | message: 'Browse workspace', 70 | child: ListTile( 71 | title: const Text("Explorer"), 72 | leading: Icon( 73 | FluentIcons.compass_northwest_20_regular, 74 | ), 75 | onTap: () async { 76 | await Navigator.of(context).pushNamed( 77 | WorkspaceExplorerScreenRoute, 78 | arguments: folder, 79 | ); 80 | await Navigator.of(context).pop(); 81 | }, 82 | ), 83 | ), 84 | // ListTile( 85 | // title: const Text("Search"), 86 | // leading: Icon( 87 | // FluentIcons.search_20_regular, 88 | // ), 89 | // ), 90 | // ListTile( 91 | // title: const Text("Source control"), 92 | // leading: Icon(Icons.timeline), 93 | // ), 94 | // ListTile( 95 | // title: const Text("Run"), 96 | // leading: Icon( 97 | // FluentIcons.arrow_right_circle_24_regular, 98 | // ), 99 | // ), 100 | ListTile( 101 | title: const Text("Terminal"), 102 | leading: Icon( 103 | FluentIcons.code_20_regular, 104 | ), 105 | onTap: () { 106 | Navigator.pushNamed(context, TerminalScreenRoute); 107 | }, 108 | ), 109 | ColoredListTile( 110 | isDark, 111 | Colors.grey, 112 | () { 113 | Navigator.of(context).popAndPushNamed(SettingsScreenRoute); 114 | }, 115 | const Text("Settings"), 116 | Icon( 117 | FluentIcons.settings_20_regular, 118 | ), 119 | ), 120 | ColoredListTile( 121 | isDark, 122 | Colors.red, 123 | () { 124 | Navigator.of(context).pushReplacementNamed(StartScreenRoute); 125 | }, 126 | const Text("Close"), 127 | Icon( 128 | FluentIcons.dismiss_circle_20_regular, 129 | ), 130 | ), 131 | ], 132 | ), 133 | ), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/creamy_field/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_highlighter: 59 | dependency: "direct main" 60 | description: 61 | name: flutter_highlighter 62 | url: "https://pub.dartlang.org" 63 | source: hosted 64 | version: "0.1.1" 65 | flutter_lints: 66 | dependency: "direct dev" 67 | description: 68 | name: flutter_lints 69 | url: "https://pub.dartlang.org" 70 | source: hosted 71 | version: "1.0.4" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | highlighter: 78 | dependency: "direct main" 79 | description: 80 | name: highlighter 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.1.1" 84 | lints: 85 | dependency: transitive 86 | description: 87 | name: lints 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.0.1" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.12.11" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.7.0" 105 | path: 106 | dependency: transitive 107 | description: 108 | name: path 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.8.0" 112 | sky_engine: 113 | dependency: transitive 114 | description: flutter 115 | source: sdk 116 | version: "0.0.99" 117 | source_span: 118 | dependency: transitive 119 | description: 120 | name: source_span 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.8.1" 124 | stack_trace: 125 | dependency: transitive 126 | description: 127 | name: stack_trace 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.10.0" 131 | stream_channel: 132 | dependency: transitive 133 | description: 134 | name: stream_channel 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "2.1.0" 138 | string_scanner: 139 | dependency: transitive 140 | description: 141 | name: string_scanner 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.1.0" 145 | term_glyph: 146 | dependency: transitive 147 | description: 148 | name: term_glyph 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.2.0" 152 | test_api: 153 | dependency: transitive 154 | description: 155 | name: test_api 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "0.4.3" 159 | typed_data: 160 | dependency: transitive 161 | description: 162 | name: typed_data 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.3.0" 166 | vector_math: 167 | dependency: transitive 168 | description: 169 | name: vector_math 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "2.1.1" 173 | sdks: 174 | dart: ">=2.15.0 <3.0.0" 175 | flutter: ">=2.8.0" 176 | -------------------------------------------------------------------------------- /org.purplegraphite.code/assets/fonts/Source_Code_Pro/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /packages/creamy_field/example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | creamy_field: 47 | dependency: "direct main" 48 | description: 49 | path: ".." 50 | relative: true 51 | source: path 52 | version: "0.4.1" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.2" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.0" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_highlighter: 73 | dependency: transitive 74 | description: 75 | name: flutter_highlighter 76 | url: "https://pub.dartlang.org" 77 | source: hosted 78 | version: "0.1.1" 79 | flutter_lints: 80 | dependency: "direct dev" 81 | description: 82 | name: flutter_lints 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "1.0.4" 86 | flutter_test: 87 | dependency: "direct dev" 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | highlighter: 92 | dependency: transitive 93 | description: 94 | name: highlighter 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.1.1" 98 | lints: 99 | dependency: transitive 100 | description: 101 | name: lints 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.0.1" 105 | matcher: 106 | dependency: transitive 107 | description: 108 | name: matcher 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.12.11" 112 | meta: 113 | dependency: transitive 114 | description: 115 | name: meta 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.7.0" 119 | path: 120 | dependency: transitive 121 | description: 122 | name: path 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.8.0" 126 | sky_engine: 127 | dependency: transitive 128 | description: flutter 129 | source: sdk 130 | version: "0.0.99" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.8.1" 138 | stack_trace: 139 | dependency: transitive 140 | description: 141 | name: stack_trace 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.10.0" 145 | stream_channel: 146 | dependency: transitive 147 | description: 148 | name: stream_channel 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.0" 152 | string_scanner: 153 | dependency: transitive 154 | description: 155 | name: string_scanner 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.1.0" 159 | term_glyph: 160 | dependency: transitive 161 | description: 162 | name: term_glyph 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.2.0" 166 | test_api: 167 | dependency: transitive 168 | description: 169 | name: test_api 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.4.3" 173 | typed_data: 174 | dependency: transitive 175 | description: 176 | name: typed_data 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.3.0" 180 | vector_math: 181 | dependency: transitive 182 | description: 183 | name: vector_math 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.1.1" 187 | sdks: 188 | dart: ">=2.15.0 <3.0.0" 189 | flutter: ">=2.8.0" 190 | -------------------------------------------------------------------------------- /org.purplegraphite.code/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: code 2 | description: Snake code editor 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 0.5.1-alpha+210410 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | provider: ^5.0.0 28 | fluentui_system_icons: ^1.1.104 29 | eva_icons_flutter: ^3.0.0 30 | shared_preferences: ^2.0.3 31 | path_provider: ^2.0.1 32 | path: ^1.8.0 33 | permission_handler: ^6.0.1 34 | hive: ^2.0.0 35 | hive_flutter: ^1.0.0 36 | creamy_field: ^0.4.0 37 | crypto: ^3.0.0 38 | logging: ^1.0.0 39 | google_fonts: ^2.0.0 40 | file_picker: ^3.0.0 41 | equatable: ^2.0.0 42 | share: ^2.0.1 43 | 44 | dev_dependencies: 45 | lint: ^1.5.3 46 | pedantic: ^1.11.0 47 | flutter_test: 48 | sdk: flutter 49 | # hive_generator fails version resolving 50 | # hive_generator: ^1.0.0 51 | build_runner: ^1.12.2 52 | 53 | dependency_overrides: 54 | creamy_field: 55 | path: ../packages/creamy_field/ 56 | 57 | # For information on the generic Dart part of this file, see the 58 | # following page: https://dart.dev/tools/pub/pubspec 59 | 60 | # The following section is specific to Flutter. 61 | flutter: 62 | 63 | # The following line ensures that the Material Icons font is 64 | # included with your application, so that you can use the icons in 65 | # the material Icons class. 66 | uses-material-design: true 67 | fonts: 68 | - family: SourceCodePro 69 | fonts: 70 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf 71 | weight: 100 72 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-ExtraLightItalic.ttf 73 | weight: 100 74 | style: italic 75 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Light.ttf 76 | weight: 300 77 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-LightItalic.ttf 78 | weight: 300 79 | style: italic 80 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf 81 | weight: 400 82 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Italic.ttf 83 | weight: 400 84 | style: italic 85 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf 86 | weight: 500 87 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-MediumItalic.ttf 88 | weight: 500 89 | style: italic 90 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-SemiBold.ttf 91 | weight: 700 92 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-SemiBoldItalic.ttf 93 | weight: 700 94 | style: italic 95 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf 96 | weight: 800 97 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-BoldItalic.ttf 98 | weight: 800 99 | style: italic 100 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-Black.ttf 101 | weight: 900 102 | - asset: assets/fonts/Source_Code_Pro/SourceCodePro-BlackItalic.ttf 103 | weight: 900 104 | style: italic 105 | # To add assets to your application, add an assets section, like this: 106 | # assets: 107 | # - images/a_dot_burr.jpeg 108 | # - images/a_dot_ham.jpeg 109 | 110 | # An image asset can refer to one or more resolution-specific "variants", see 111 | # https://flutter.dev/assets-and-images/#resolution-aware. 112 | 113 | # For details regarding adding assets from package dependencies, see 114 | # https://flutter.dev/assets-and-images/#from-packages 115 | 116 | # To add custom fonts to your application, add a fonts section here, 117 | # in this "flutter" section. Each entry in this list should have a 118 | # "family" key with the font family name, and a "fonts" key with a 119 | # list giving the asset and other descriptors for the font. For 120 | # example: 121 | # fonts: 122 | # - family: Schyler 123 | # fonts: 124 | # - asset: fonts/Schyler-Regular.ttf 125 | # - asset: fonts/Schyler-Italic.ttf 126 | # style: italic 127 | # - family: Trajan Pro 128 | # fonts: 129 | # - asset: fonts/TrajanPro.ttf 130 | # - asset: fonts/TrajanPro_Bold.ttf 131 | # weight: 700 132 | # 133 | # For details regarding fonts from package dependencies, 134 | # see https://flutter.dev/custom-fonts/#from-packages 135 | -------------------------------------------------------------------------------- /packages/creamy_field/test/creamy_field_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:creamy_field/creamy_field.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | const String initialText = 6 | """Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum; 7 | A first-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin. 8 | 9 | Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. 10 | Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well. """; 11 | 12 | void main() { 13 | group('Tests for descriptions obtained from CreamyEditingController: ', () { 14 | test('Text description test - 1', () { 15 | const int _baseOffset = 0, _extentOffset = 11; 16 | final CreamyEditingController textController1 = 17 | CreamyEditingController.fromValue( 18 | TextEditingValue( 19 | text: initialText, 20 | selection: const TextSelection( 21 | baseOffset: _baseOffset, 22 | extentOffset: _extentOffset, 23 | ), 24 | ), 25 | ); 26 | expect(textController1.selectedText, 27 | initialText.substring(_baseOffset, _extentOffset)); 28 | expect(textController1.totalLineCount, 5); 29 | expect(textController1.atLine, 1); 30 | expect(textController1.atColumn, 12); 31 | expect(textController1.baseColumn, 1); 32 | expect(textController1.extentColumn, 12); 33 | }); 34 | 35 | test('Text description test - 2', () { 36 | const int _baseOffset = 79, _extentOffset = 94; 37 | final CreamyEditingController textController2 = 38 | CreamyEditingController.fromValue( 39 | TextEditingValue( 40 | text: initialText, 41 | selection: const TextSelection( 42 | baseOffset: _baseOffset, 43 | extentOffset: _extentOffset, 44 | ), 45 | ), 46 | ); 47 | expect(textController2.selectedText, 48 | initialText.substring(_baseOffset, _extentOffset)); 49 | expect(textController2.totalLineCount, 5); 50 | expect(textController2.atLine, 2); 51 | expect(textController2.atColumn, 16); 52 | expect(textController2.baseColumn, 1); 53 | expect(textController2.extentColumn, 16); 54 | }); 55 | 56 | test('Text description test - 3', () { 57 | int _baseOffset = initialText.length - 7, 58 | _extentOffset = initialText.length; 59 | final CreamyEditingController textController3 = 60 | CreamyEditingController.fromValue( 61 | TextEditingValue( 62 | text: initialText, 63 | selection: TextSelection( 64 | baseOffset: _baseOffset, 65 | extentOffset: _extentOffset, 66 | ), 67 | ), 68 | ); 69 | expect(textController3.selectedText, 70 | initialText.substring(_baseOffset, _extentOffset)); 71 | expect(textController3.totalLineCount, 5); 72 | expect(textController3.atLine, 5); 73 | expect(textController3.atColumn, 281); 74 | expect(textController3.baseColumn, 274); 75 | expect(textController3.extentColumn, 281); 76 | }); 77 | }); 78 | 79 | group( 80 | 'When using CreamyEditingController, are \\t getting replaced with spaces when', 81 | () { 82 | late CreamyEditingController controller; 83 | final int tabSize = 4; 84 | final String textWithSpaces = 'hello${' ' * tabSize}world'; 85 | final String textWithTabs = 'hello\tworld'; 86 | setUp(() { 87 | controller = CreamyEditingController( 88 | text: textWithTabs, 89 | tabSize: tabSize, 90 | ); 91 | controller.selection = TextSelection.fromPosition( 92 | TextPosition(offset: controller.text.length)); 93 | }); 94 | test('the constructor is initialized with a text containing `\\t`', () { 95 | expect( 96 | controller.text, 97 | textWithSpaces, 98 | reason: 99 | 'The number of spaces replaced by single tab is not equal to tabSize', 100 | ); 101 | }); 102 | test('text is changed', () { 103 | final String newLineWithTab = '\n\tAdded tab on start of this line'; 104 | final String newLineWithSpaces = 105 | '\n${' ' * tabSize}Added tab on start of this line'; 106 | controller.text = '${controller.text}$newLineWithTab'; 107 | expect(controller.text, textWithSpaces + newLineWithSpaces); 108 | }); 109 | 110 | test('\\t is added to the end', () { 111 | final String previousText = controller.text; 112 | controller.addTab(); 113 | final int newBaseOffset = controller.selection.baseOffset; 114 | final String newText = controller.text; 115 | expect(newBaseOffset, controller.text.length, 116 | reason: 'cursor was not moved to the end'); 117 | expect(newText, previousText + (' ' * tabSize), 118 | reason: 119 | 'At the end, the number of spaces replaced by single tab is not equal to tabSize'); 120 | }); 121 | 122 | test('\\t is added not in the end or start', () { 123 | final String previousText = controller.text; 124 | final int offset = (previousText.length / 2).round(); 125 | controller.selection = TextSelection.fromPosition(TextPosition( 126 | offset: offset, 127 | )); 128 | final int oldBaseOffset = controller.selection.baseOffset; 129 | controller.addTab(); 130 | final int newBaseOffset = controller.selection.baseOffset; 131 | final String newText = controller.text; 132 | expect(newBaseOffset, oldBaseOffset + tabSize, 133 | reason: 134 | 'cursor was not moved to the correct position after tabs where replaced with spaces, previous offset: ${previousText.length}, tabSize: $tabSize'); 135 | expect( 136 | newText, 137 | previousText.substring(0, offset) + 138 | (' ' * tabSize) + 139 | previousText.substring(offset, previousText.length), 140 | reason: 141 | 'At the end, the number of spaces replaced by single tab is not equal to tabSize'); 142 | }); 143 | tearDown(() { 144 | controller.dispose(); 145 | }); 146 | }); 147 | } 148 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/start_tips/tips.dart: -------------------------------------------------------------------------------- 1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'dart:math'; 5 | 6 | import 'package:provider/provider.dart'; 7 | 8 | class StartTipsController with ChangeNotifier { 9 | StartScreenController() { 10 | _generateRandomIndexForTip(); 11 | } 12 | 13 | // TIPS 14 | 15 | bool _showTips = true; 16 | 17 | bool get showTips => _showTips ?? false; 18 | 19 | int _tipIndex = 0; 20 | 21 | void _generateRandomIndexForTip() { 22 | final DateTime time = DateTime.now(); 23 | final Random rand = Random(time.millisecond); 24 | _tipIndex = rand.nextInt(_tips.length); 25 | } 26 | 27 | void expandTips() { 28 | _showTips = true; 29 | notifyListeners(); 30 | } 31 | 32 | void collapseTips() { 33 | _showTips = false; 34 | notifyListeners(); 35 | } 36 | 37 | int getTipIndex() { 38 | return _tipIndex; 39 | } 40 | 41 | void nextTip() { 42 | final _potentialIndex = _tipIndex + 1; 43 | _tipIndex = _potentialIndex == _tips.length ? 0 : _potentialIndex; 44 | notifyListeners(); 45 | } 46 | 47 | void previousTip() { 48 | final _potentialIndex = _tipIndex - 1; 49 | _tipIndex = _potentialIndex == -1 ? (_tips.length - 1) : _potentialIndex; 50 | notifyListeners(); 51 | } 52 | 53 | final List _tips = [ 54 | 'Files are good for writing code snippets, general programs & short scripts. Opening a directory as a project will allow working with multiple files.', 55 | 'You can open directory from Termux app storage if you tap on import while keeping Termux app open in background!', 56 | 'Your duration of work in a folder or on a file with last modified time will be used to offer suggestions later' 57 | ]; 58 | 59 | List get tips => _tips ?? []; 60 | } 61 | 62 | class StartTips extends StatelessWidget { 63 | @override 64 | Widget build(BuildContext context) { 65 | final _theme = Theme.of(context); 66 | final _isDarkMode = _theme.brightness == Brightness.dark; 67 | final _borderRadius = BorderRadius.circular(10); 68 | final foregroundColorOnDarkBackground = 69 | _isDarkMode ? Colors.white.withOpacity(1) : Color(0xEE212121); 70 | return Padding( 71 | padding: const EdgeInsets.only(left: 8, bottom: 16), 72 | child: Consumer( 73 | builder: (context, value, child) { 74 | if (!value.showTips) { 75 | return Padding( 76 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 77 | child: Flex( 78 | direction: Axis.horizontal, 79 | children: [ 80 | Material( 81 | borderRadius: _borderRadius, 82 | elevation: _isDarkMode ? 0 : 2, 83 | child: InkWell( 84 | onTap: value.expandTips, 85 | borderRadius: _borderRadius, 86 | child: Padding( 87 | padding: const EdgeInsets.symmetric( 88 | horizontal: 8.0, vertical: 4), 89 | child: Icon(EvaIcons.moreHorizontalOutline), 90 | ), 91 | ), 92 | ), 93 | ], 94 | ), 95 | ); 96 | } 97 | final int _index = value.getTipIndex(); 98 | return Padding( 99 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 100 | child: Material( 101 | borderRadius: _borderRadius, 102 | elevation: _isDarkMode ? 0 : 2, 103 | child: Padding( 104 | padding: 105 | const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), 106 | child: Stack( 107 | children: [ 108 | Column( 109 | children: [ 110 | Padding( 111 | padding: const EdgeInsets.only(left: 8.0), 112 | child: Row( 113 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 114 | children: [ 115 | Text( 116 | 'TIP #${_index + 1}', 117 | style: _theme.textTheme.subtitle1.copyWith( 118 | color: foregroundColorOnDarkBackground, 119 | ), 120 | ), 121 | IconButton( 122 | padding: const EdgeInsets.all(0), 123 | icon: Icon(EvaIcons.close), 124 | color: foregroundColorOnDarkBackground, 125 | onPressed: value.collapseTips, 126 | ), 127 | ], 128 | ), 129 | ), 130 | Padding( 131 | padding: const EdgeInsets.only(left: 8, right: 8), 132 | child: Text( 133 | value.tips[_index], 134 | style: _theme.textTheme.subtitle2.copyWith( 135 | color: foregroundColorOnDarkBackground, 136 | ), 137 | ), 138 | ), 139 | Padding( 140 | padding: const EdgeInsets.only(left: 8.0, bottom: 8), 141 | child: Row( 142 | mainAxisAlignment: MainAxisAlignment.end, 143 | children: [ 144 | IconButton( 145 | icon: Icon(EvaIcons.chevronLeftOutline), 146 | color: foregroundColorOnDarkBackground, 147 | onPressed: value.previousTip, 148 | ), 149 | IconButton( 150 | icon: Icon(EvaIcons.chevronRightOutline), 151 | color: foregroundColorOnDarkBackground, 152 | onPressed: value.nextTip, 153 | ), 154 | ], 155 | ), 156 | ), 157 | ], 158 | ), 159 | // Close button above the tip 160 | ], 161 | ), 162 | ), 163 | ), 164 | ); 165 | }, 166 | ), 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/text_tools/toolbar_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/rendering.dart' show TextSelectionPoint; 3 | import 'package:flutter/widgets.dart'; 4 | 5 | import '../../creamy_field.dart'; 6 | 7 | // Intermediate data used for building menu items with the _getItems method. 8 | class CreamyTextSelectionToolbarAction { 9 | final String label; 10 | final bool visible; 11 | final void Function() onPressed; 12 | 13 | const CreamyTextSelectionToolbarAction({ 14 | required this.label, 15 | required this.onPressed, 16 | this.visible = true, 17 | }); 18 | } 19 | 20 | enum TextSelectionControlsType { 21 | material, 22 | } 23 | 24 | /// Normally, when a [TextSelectionControls] calls [buildToolbar], the UI for the toolbar is painted. 25 | /// By default, this doesn't support additional actions other than copy, cut, select all & paste. 26 | mixin CreamyTextSelectionControls implements TextSelectionControls { 27 | /// Builds a toolbar without additional actions 28 | @nonVirtual 29 | @override 30 | Widget buildToolbar( 31 | BuildContext context, 32 | Rect globalEditableRegion, 33 | double textLineHeight, 34 | Offset selectionMidpoint, 35 | List endpoints, 36 | TextSelectionDelegate delegate, 37 | ClipboardStatusNotifier clipboardStatus, 38 | Offset? lastSecondaryTapDownPosition, 39 | ) { 40 | return buildToolbarWithActions( 41 | context, 42 | globalEditableRegion, 43 | textLineHeight, 44 | selectionMidpoint, 45 | endpoints, 46 | delegate, 47 | clipboardStatus, 48 | lastSecondaryTapDownPosition, 49 | ); 50 | } 51 | 52 | /// Build a toolbar which supports actions. 53 | /// 54 | /// [CreamyTextSelectionControlsProvider] uses this to create toolbar with actions. 55 | /// 56 | /// This callback is also called by the [buildToolbar] to create a toolbar without actions. 57 | Widget buildToolbarWithActions( 58 | BuildContext context, 59 | Rect globalEditableRegion, 60 | double textLineHeight, 61 | Offset position, 62 | List endpoints, 63 | TextSelectionDelegate delegate, 64 | ClipboardStatusNotifier clipboardStatus, 65 | Offset? lastSecondaryTapDownPosition, { 66 | List actions, 67 | }); 68 | } 69 | 70 | typedef List ActionsBuilderCallback( 71 | BuildContext context, 72 | TextSelectionDelegate delegate, 73 | ClipboardStatusNotifier clipboardStatus, 74 | ); 75 | 76 | /// A [TextSelectionControls] provider with actions. 77 | /// 78 | /// Provide additional options to the text selection toolbar. 79 | /// 80 | /// This delegates methods from [controls] to allow compatiblity with Material Text input fields. 81 | class CreamyTextSelectionControlsProvider implements TextSelectionControls { 82 | final CreamyTextSelectionControls controls; 83 | 84 | /// More actions the selection toolbar should offer. 85 | ActionsBuilderCallback actionsBuilder; 86 | 87 | /// A Provider of a custom [CreamyTextSelectionControls] with actions. 88 | CreamyTextSelectionControlsProvider.custom({ 89 | required this.controls, 90 | required this.actionsBuilder, 91 | }); 92 | 93 | /// Provide text selection controls from this package, 94 | /// 95 | /// Describe the type of selections controls with [type]. 96 | /// Use [actionsBuilder] to build actions for the selection controls. 97 | factory CreamyTextSelectionControlsProvider({ 98 | required TextSelectionControlsType type, 99 | required ActionsBuilderCallback actionsBuilder, 100 | }) { 101 | TextSelectionControls textSelectionControls; 102 | 103 | switch (type) { 104 | case TextSelectionControlsType.material: 105 | default: 106 | textSelectionControls = creamyMaterialTextSelectionControls; 107 | } 108 | 109 | return CreamyTextSelectionControlsProvider.custom( 110 | controls: textSelectionControls as CreamyTextSelectionControls, 111 | actionsBuilder: actionsBuilder, 112 | ); 113 | } 114 | 115 | @override 116 | Widget buildHandle( 117 | BuildContext context, 118 | TextSelectionHandleType type, 119 | double textLineHeight, [ 120 | VoidCallback? onTap, 121 | double? startGlyphHeight, 122 | double? endGlyphHeight, 123 | ]) => 124 | controls.buildHandle( 125 | context, 126 | type, 127 | textLineHeight, 128 | onTap, 129 | startGlyphHeight, 130 | endGlyphHeight, 131 | ); 132 | 133 | @override 134 | Widget buildToolbar( 135 | BuildContext context, 136 | Rect globalEditableRegion, 137 | double textLineHeight, 138 | Offset position, 139 | List endpoints, 140 | TextSelectionDelegate delegate, 141 | ClipboardStatusNotifier clipboardStatus, 142 | Offset? lastSecondaryTapDownPosition) { 143 | return controls.buildToolbarWithActions( 144 | context, 145 | globalEditableRegion, 146 | textLineHeight, 147 | position, 148 | endpoints, 149 | delegate, 150 | clipboardStatus, 151 | lastSecondaryTapDownPosition, 152 | actions: actionsBuilder( 153 | context, 154 | delegate, 155 | clipboardStatus, 156 | ), 157 | ); 158 | } 159 | 160 | @override 161 | Offset getHandleAnchor( 162 | TextSelectionHandleType type, 163 | double textLineHeight, [ 164 | double? startGlyphHeight, 165 | double? endGlyphHeight, 166 | ]) => 167 | controls.getHandleAnchor( 168 | type, 169 | textLineHeight, 170 | startGlyphHeight, 171 | endGlyphHeight, 172 | ); 173 | 174 | @override 175 | Size getHandleSize(double textLineHeight) => controls.getHandleSize( 176 | textLineHeight, 177 | ); 178 | 179 | @override 180 | bool canCopy(TextSelectionDelegate delegate) => controls.canCopy(delegate); 181 | 182 | @override 183 | bool canCut(TextSelectionDelegate delegate) => controls.canCut(delegate); 184 | 185 | @override 186 | bool canPaste(TextSelectionDelegate delegate) => controls.canPaste(delegate); 187 | 188 | @override 189 | bool canSelectAll(TextSelectionDelegate delegate) => 190 | controls.canSelectAll(delegate); 191 | 192 | @override 193 | void handleCopy( 194 | TextSelectionDelegate delegate, 195 | ClipboardStatusNotifier? clipboardStatus, 196 | ) => 197 | controls.handleCopy( 198 | delegate, 199 | clipboardStatus, 200 | ); 201 | 202 | @override 203 | void handleCut(TextSelectionDelegate delegate, 204 | ClipboardStatusNotifier? clipboardStatus) => 205 | controls.handleCut(delegate, clipboardStatus); 206 | 207 | @override 208 | Future handlePaste(TextSelectionDelegate delegate) => 209 | controls.handlePaste(delegate); 210 | 211 | @override 212 | void handleSelectAll(TextSelectionDelegate delegate) => 213 | controls.handleSelectAll(delegate); 214 | } 215 | -------------------------------------------------------------------------------- /org.purplegraphite.code/lib/src/ui/components/about/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:code/src/common/ui.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 4 | 5 | import '../../../common/strings.dart'; 6 | 7 | void showMyAboutDialog({ 8 | @required BuildContext context, 9 | String applicationName, 10 | String applicationVersion, 11 | Widget applicationIcon, 12 | String applicationLegalese, 13 | List children, 14 | bool useRootNavigator = true, 15 | RouteSettings routeSettings, 16 | }) { 17 | assert(context != null); 18 | assert(useRootNavigator != null); 19 | showDialog( 20 | context: context, 21 | useRootNavigator: useRootNavigator, 22 | builder: (BuildContext context) { 23 | return AboutDialog( 24 | applicationVersion: applicationVersion, 25 | applicationIcon: applicationIcon, 26 | applicationLegalese: applicationLegalese, 27 | children: children, 28 | ); 29 | }, 30 | routeSettings: routeSettings, 31 | ); 32 | } 33 | 34 | class AboutDialog extends StatelessWidget { 35 | /// Creates an about box. 36 | /// 37 | /// The arguments are all optional. The application name, if omitted, will be 38 | /// derived from the nearest [Title] widget. The version, icon, and legalese 39 | /// values default to the empty string. 40 | const AboutDialog({ 41 | Key key, 42 | this.applicationVersion, 43 | this.applicationIcon, 44 | this.applicationLegalese, 45 | this.children, 46 | }) : super(key: key); 47 | 48 | /// The version of this build of the application. 49 | /// 50 | /// This string is shown under the application name. 51 | /// 52 | /// Defaults to the empty string. 53 | final String applicationVersion; 54 | 55 | /// The icon to show next to the application name. 56 | /// 57 | /// By default no icon is shown. 58 | /// 59 | /// Typically this will be an [ImageIcon] widget. It should honor the 60 | /// [IconTheme]'s [IconThemeData.size]. 61 | final Widget applicationIcon; 62 | 63 | /// A string to show in small print. 64 | /// 65 | /// Typically this is a copyright notice. 66 | /// 67 | /// Defaults to the empty string. 68 | final String applicationLegalese; 69 | 70 | /// Widgets to add to the dialog box after the name, version, and legalese. 71 | /// 72 | /// This could include a link to a Web site, some descriptive text, credits, 73 | /// or other information to show in the about box. 74 | /// 75 | /// Defaults to nothing. 76 | final List children; 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | // final String name = Strings.title; 81 | final String version = applicationVersion; 82 | final Widget icon = applicationIcon; 83 | final TextStyle _outlineButtonTextStyle = 84 | Theme.of(context).textTheme.button.copyWith( 85 | fontSize: 12, 86 | ); 87 | // TODO(predatorx7) check AlertDialog's scrollable true 88 | return Dialog( 89 | // backgroundColor: Colors.lightGreen[100], 90 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), 91 | clipBehavior: Clip.hardEdge, 92 | child: Column( 93 | mainAxisSize: MainAxisSize.min, 94 | crossAxisAlignment: CrossAxisAlignment.stretch, 95 | children: [ 96 | Row( 97 | children: [ 98 | IconButton( 99 | icon: Icon(EvaIcons.close), 100 | tooltip: 'close', 101 | onPressed: () => Navigator.of(context).maybePop(), 102 | ) 103 | ], 104 | ), 105 | Padding( 106 | padding: const EdgeInsets.fromLTRB(24, 0, 24, 24), 107 | child: ListBody( 108 | children: [ 109 | if (icon != null) 110 | IconTheme(data: Theme.of(context).iconTheme, child: icon), 111 | Padding( 112 | padding: const EdgeInsets.only(top: 4.0, bottom: 8), 113 | child: RichText( 114 | text: TextSpan( 115 | text: 'version ', 116 | style: Theme.of(context).textTheme.bodyText2.copyWith( 117 | fontWeight: FontWeight.bold, 118 | fontStyle: FontStyle.italic, 119 | color: Colors.green), 120 | children: [ 121 | TextSpan( 122 | text: version, 123 | style: Theme.of(context).textTheme.bodyText2.copyWith( 124 | fontStyle: FontStyle.italic, 125 | ), 126 | ), 127 | ], 128 | ), 129 | ), 130 | ), 131 | ...?children, 132 | SizedBox( 133 | height: 10, 134 | ), 135 | // Padding( 136 | // padding: const EdgeInsets.only(top: 10.0, bottom: 5), 137 | // child: ListBody( 138 | // children: [ 139 | // Container(height: 18.0), 140 | // Text( 141 | // Strings.applicationLegalese ?? '', 142 | // style: Theme.of(context).textTheme.caption, 143 | // textAlign: TextAlign.left, 144 | // ), 145 | // ], 146 | // ), 147 | // ), 148 | Tooltip( 149 | message: 'Contact us', 150 | child: OutlineButton( 151 | onPressed: () { 152 | print('Contact us'); 153 | }, 154 | child: Text( 155 | Strings.contactUs, 156 | style: _outlineButtonTextStyle, 157 | ), 158 | shape: Corners.outlinedShapeBorder), 159 | ), 160 | Tooltip( 161 | message: 'View licenses', 162 | child: OutlineButton( 163 | child: Text( 164 | Strings.viewLicenses, 165 | style: _outlineButtonTextStyle, 166 | ), 167 | onPressed: () { 168 | showLicensePage( 169 | context: context, 170 | applicationName: '', 171 | applicationVersion: applicationVersion, 172 | applicationIcon: applicationIcon, 173 | applicationLegalese: applicationLegalese, 174 | ); 175 | }, 176 | shape: Corners.outlinedShapeBorder, 177 | ), 178 | ), 179 | ], 180 | ), 181 | ), 182 | ], 183 | ), 184 | ); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /packages/creamy_field/lib/src/syntax_highlighter/creamy_syntax_highlighter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Mushaheed Syed. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD 3-Clause License that can be 4 | // found in the LICENSE file. 5 | // -------------------------------------------------------------------------------- 6 | // MIT License 7 | // 8 | // Copyright (c) 2019 Rongjian Zhang 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import 'package:flutter/material.dart'; 29 | import 'package:flutter/widgets.dart'; 30 | import 'package:flutter_highlighter/themes/dark.dart' as dark_theme_highlight; 31 | import 'package:highlighter/highlighter.dart' show highlight, Node; 32 | 33 | import './highlighted_theme_type.dart'; 34 | import './language_type.dart'; 35 | import '../syntax_highlighter.dart'; 36 | 37 | /// Highlights a source code's syntax based on language type & theme type. 38 | /// 39 | /// This highlighter internally uses [flutter_highlight](https://pub.dev/packages/flutter_highlighter) 40 | /// & [highlight](https://pub.dev/packages/highlighter) 41 | class CreamySyntaxHighlighter implements SyntaxHighlighter { 42 | // The original code to be highlighted 43 | late String _source; 44 | 45 | /// Highlight language 46 | /// 47 | /// If null then no syntax highlighting will be done. 48 | /// 49 | /// [All available languages](https://github.com/Purple-Graphite/highlighter/tree/master/highlighter/lib/languages) 50 | final LanguageType language; 51 | 52 | final String? _language; 53 | 54 | /// Highlight theme 55 | /// 56 | /// Defaults to a light theme. 57 | /// 58 | /// [All available themes](https://github.com/Purple-Graphite/highlighter/blob/master/flutter_highlighter/lib/themes) 59 | final HighlightedThemeType theme; 60 | 61 | Map? _theme; 62 | 63 | /// The dark theme used by this highlighter when [brightness] is [Brightness.dark] 64 | /// 65 | /// Defaults to [dark_theme_highlight.darkTheme]. 66 | final HighlightedThemeType? darkTheme; 67 | 68 | Map? _darkTheme; 69 | 70 | /// The theme used by this syntax highlighter. 71 | /// If brightness is dark, [darkTheme] is used and when it's light or null, [theme] is used. 72 | final ThemeMode? themeMode; 73 | 74 | Brightness? _brightness; 75 | 76 | /// The Theme brightness of the syntax highlight 77 | Brightness? get brightness => _brightness; 78 | 79 | /// Provide the context to this highligher. This method will be used to update 80 | /// the brighness based on context in [RichEditableText] 81 | /// 82 | /// If you're not using [CreamyField] or [RichEditableText] then use this 83 | /// to obtain context as it's required to get context. 84 | void withContext(BuildContext context) { 85 | // change brightness 86 | switch (themeMode) { 87 | case ThemeMode.light: 88 | _brightness = Brightness.light; 89 | break; 90 | case ThemeMode.dark: 91 | _brightness = Brightness.dark; 92 | break; 93 | case ThemeMode.system: 94 | default: 95 | _brightness = Theme.of(context).brightness; 96 | break; 97 | } 98 | } 99 | 100 | // The effective theme brighness of this syntax highlighter. 101 | Brightness get _effectiveBrightness => _brightness ?? Brightness.light; 102 | 103 | bool get _isThemeDark => _effectiveBrightness == Brightness.dark; 104 | 105 | Map? get _effectiveTheme { 106 | // Returning the regular theme as dark theme is null. 107 | if (_darkTheme == null) return _theme; 108 | // If theme mode is dark returns a dark theme, else returns the regular theme 109 | return _isThemeDark ? _darkTheme : _theme; 110 | } 111 | 112 | /// Creates a syntax highlighter. 113 | /// 114 | /// You can specify the language mode & theme type with [language], [theme], [darkTheme], [brightness] respectively. 115 | /// 116 | /// For the highlight language, it is recommended to give [language] a value for performance 117 | /// [All available languages](https://github.com/Purple-Graphite/highlight/tree/master/highlighter/lib/languages) 118 | /// 119 | /// The supported highlight themes are 120 | /// [All available themes](https://github.com/Purple-Graphite/highlight/blob/master/flutter_highlighter/lib/themes) 121 | /// 122 | /// syntax will not be highlighted if language is null. 123 | CreamySyntaxHighlighter({ 124 | required this.language, 125 | required this.theme, 126 | this.darkTheme, 127 | this.themeMode, 128 | }) : this._language = toLanguageName(language), 129 | this._theme = getHighlightedThemeStyle(theme), 130 | this._darkTheme = darkTheme != null 131 | ? getHighlightedThemeStyle(darkTheme) 132 | : dark_theme_highlight.darkTheme; 133 | 134 | List _convert(List nodes) { 135 | List spans = []; 136 | var currentSpans = spans; 137 | List> stack = []; 138 | 139 | void _traverse(Node node) { 140 | final TextStyle? _all = 141 | _isThemeDark ? TextStyle(color: Colors.white) : null; 142 | if (node.value != null) { 143 | currentSpans.add(node.className == null 144 | ? TextSpan(text: node.value, style: _all) 145 | : TextSpan( 146 | text: node.value, style: _effectiveTheme![node.className!])); 147 | } else if (node.children != null) { 148 | List tmp = []; 149 | currentSpans.add( 150 | TextSpan(children: tmp, style: _effectiveTheme![node.className!])); 151 | stack.add(currentSpans); 152 | currentSpans = tmp; 153 | 154 | node.children!.forEach((n) { 155 | _traverse(n); 156 | if (n == node.children!.last) { 157 | currentSpans = stack.isEmpty ? spans : stack.removeLast(); 158 | } 159 | }); 160 | } 161 | } 162 | 163 | for (var node in nodes) { 164 | _traverse(node); 165 | } 166 | 167 | return spans; 168 | } 169 | 170 | @override 171 | List parseTextEditingValue(TextEditingValue? value) { 172 | _source = value!.text; 173 | return _convert(highlight 174 | .parse(_source, language: _language, autoDetection: _language == null) 175 | .nodes!); 176 | } 177 | } 178 | --------------------------------------------------------------------------------