android/ios-adaption feature, markdown, and augment #6
							
								
								
									
										30
									
								
								.metadata
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
# This file should be version controlled and should not be manually edited.
 | 
			
		||||
 | 
			
		||||
version:
 | 
			
		||||
  revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
 | 
			
		||||
  revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2"
 | 
			
		||||
  channel: "stable"
 | 
			
		||||
 | 
			
		||||
project_type: app
 | 
			
		||||
| 
						 | 
				
			
			@ -13,26 +13,26 @@ project_type: app
 | 
			
		|||
migration:
 | 
			
		||||
  platforms:
 | 
			
		||||
    - platform: root
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: android
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: ios
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: linux
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: macos
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: web
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
    - platform: windows
 | 
			
		||||
      create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
 | 
			
		||||
      create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
      base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
 | 
			
		||||
 | 
			
		||||
  # User provided section
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								android/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
gradle-wrapper.jar
 | 
			
		||||
/.gradle
 | 
			
		||||
/captures/
 | 
			
		||||
/gradlew
 | 
			
		||||
/gradlew.bat
 | 
			
		||||
/local.properties
 | 
			
		||||
GeneratedPluginRegistrant.java
 | 
			
		||||
.cxx/
 | 
			
		||||
 | 
			
		||||
# Remember to never publicly share your keystore.
 | 
			
		||||
# See https://flutter.dev/to/reference-keystore
 | 
			
		||||
key.properties
 | 
			
		||||
**/*.keystore
 | 
			
		||||
**/*.jks
 | 
			
		||||
							
								
								
									
										52
									
								
								android/app/build.gradle.kts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id("com.android.application")
 | 
			
		||||
    id("kotlin-android")
 | 
			
		||||
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
 | 
			
		||||
    id("dev.flutter.flutter-gradle-plugin")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace = "com.example.crab_ui"
 | 
			
		||||
    compileSdk = flutter.compileSdkVersion
 | 
			
		||||
    ndkVersion = flutter.ndkVersion
 | 
			
		||||
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
        targetCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = JavaVersion.VERSION_11.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
 | 
			
		||||
        applicationId = "com.example.crab_ui"
 | 
			
		||||
        // You can update the following values to match your application needs.
 | 
			
		||||
        // For more information, see: https://flutter.dev/to/review-gradle-config.
 | 
			
		||||
        minSdk = flutter.minSdkVersion
 | 
			
		||||
        targetSdk = flutter.targetSdkVersion
 | 
			
		||||
        versionCode = flutter.versionCode
 | 
			
		||||
        versionName = flutter.versionName
 | 
			
		||||
        ndk {
 | 
			
		||||
            abiFilters.addAll(listOf("arm64-v8a", "x86_64"))
 | 
			
		||||
        }
 | 
			
		||||
        sourceSets {
 | 
			
		||||
            getByName("main") {
 | 
			
		||||
                jniLibs.srcDirs("src/main/jniLibs")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            // TODO: Add your own signing config for the release build.
 | 
			
		||||
            // Signing with the debug keys for now, so `flutter run --release` works.
 | 
			
		||||
            signingConfig = signingConfigs.getByName("debug")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
flutter {
 | 
			
		||||
    source = "../.."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								android/app/src/debug/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <!-- The INTERNET permission is required for development. Specifically,
 | 
			
		||||
         the Flutter tool needs it to communicate with the running application
 | 
			
		||||
         to allow setting breakpoints, to provide hot reload, etc.
 | 
			
		||||
    -->
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
</manifest>
 | 
			
		||||
							
								
								
									
										45
									
								
								android/app/src/main/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <application
 | 
			
		||||
        android:label="crab_ui"
 | 
			
		||||
        android:name="${applicationName}"
 | 
			
		||||
        android:icon="@mipmap/ic_launcher">
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:launchMode="singleTop"
 | 
			
		||||
            android:taskAffinity=""
 | 
			
		||||
            android:theme="@style/LaunchTheme"
 | 
			
		||||
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
 | 
			
		||||
            android:hardwareAccelerated="true"
 | 
			
		||||
            android:windowSoftInputMode="adjustResize">
 | 
			
		||||
            <!-- Specifies an Android theme to apply to this Activity as soon as
 | 
			
		||||
                 the Android process has started. This theme is visible to the user
 | 
			
		||||
                 while the Flutter UI initializes. After that, this theme continues
 | 
			
		||||
                 to determine the Window background behind the Flutter UI. -->
 | 
			
		||||
            <meta-data
 | 
			
		||||
              android:name="io.flutter.embedding.android.NormalTheme"
 | 
			
		||||
              android:resource="@style/NormalTheme"
 | 
			
		||||
              />
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN"/>
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER"/>
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
        <!-- Don't delete the meta-data below.
 | 
			
		||||
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
 | 
			
		||||
        <meta-data
 | 
			
		||||
            android:name="flutterEmbedding"
 | 
			
		||||
            android:value="2" />
 | 
			
		||||
    </application>
 | 
			
		||||
    <!-- Required to query activities that can process text, see:
 | 
			
		||||
         https://developer.android.com/training/package-visibility and
 | 
			
		||||
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
 | 
			
		||||
 | 
			
		||||
         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
 | 
			
		||||
    <queries>
 | 
			
		||||
        <intent>
 | 
			
		||||
            <action android:name="android.intent.action.PROCESS_TEXT"/>
 | 
			
		||||
            <data android:mimeType="text/plain"/>
 | 
			
		||||
        </intent>
 | 
			
		||||
    </queries>
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package com.example.crab_ui
 | 
			
		||||
 | 
			
		||||
import io.flutter.embedding.android.FlutterActivity
 | 
			
		||||
 | 
			
		||||
class MainActivity : FlutterActivity()
 | 
			
		||||
							
								
								
									
										12
									
								
								android/app/src/main/res/drawable-v21/launch_background.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Modify this file to customize your launch splash screen -->
 | 
			
		||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <item android:drawable="?android:colorBackground" />
 | 
			
		||||
 | 
			
		||||
    <!-- You can insert your own image assets here -->
 | 
			
		||||
    <!-- <item>
 | 
			
		||||
        <bitmap
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:src="@mipmap/launch_image" />
 | 
			
		||||
    </item> -->
 | 
			
		||||
</layer-list>
 | 
			
		||||
							
								
								
									
										12
									
								
								android/app/src/main/res/drawable/launch_background.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Modify this file to customize your launch splash screen -->
 | 
			
		||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <item android:drawable="@android:color/white" />
 | 
			
		||||
 | 
			
		||||
    <!-- You can insert your own image assets here -->
 | 
			
		||||
    <!-- <item>
 | 
			
		||||
        <bitmap
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:src="@mipmap/launch_image" />
 | 
			
		||||
    </item> -->
 | 
			
		||||
</layer-list>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/mipmap-hdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 544 B  | 
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/mipmap-mdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 442 B  | 
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 721 B  | 
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										18
									
								
								android/app/src/main/res/values-night/styles.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
 | 
			
		||||
    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
 | 
			
		||||
        <!-- Show a splash screen on the activity. Automatically removed when
 | 
			
		||||
             the Flutter engine draws its first frame -->
 | 
			
		||||
        <item name="android:windowBackground">@drawable/launch_background</item>
 | 
			
		||||
    </style>
 | 
			
		||||
    <!-- Theme applied to the Android Window as soon as the process has started.
 | 
			
		||||
         This theme determines the color of the Android Window while your
 | 
			
		||||
         Flutter UI initializes, as well as behind your Flutter UI while its
 | 
			
		||||
         running.
 | 
			
		||||
 | 
			
		||||
         This Theme is only used starting with V2 of Flutter's Android embedding. -->
 | 
			
		||||
    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
 | 
			
		||||
        <item name="android:windowBackground">?android:colorBackground</item>
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										18
									
								
								android/app/src/main/res/values/styles.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
 | 
			
		||||
    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
 | 
			
		||||
        <!-- Show a splash screen on the activity. Automatically removed when
 | 
			
		||||
             the Flutter engine draws its first frame -->
 | 
			
		||||
        <item name="android:windowBackground">@drawable/launch_background</item>
 | 
			
		||||
    </style>
 | 
			
		||||
    <!-- Theme applied to the Android Window as soon as the process has started.
 | 
			
		||||
         This theme determines the color of the Android Window while your
 | 
			
		||||
         Flutter UI initializes, as well as behind your Flutter UI while its
 | 
			
		||||
         running.
 | 
			
		||||
 | 
			
		||||
         This Theme is only used starting with V2 of Flutter's Android embedding. -->
 | 
			
		||||
    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
 | 
			
		||||
        <item name="android:windowBackground">?android:colorBackground</item>
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										7
									
								
								android/app/src/profile/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <!-- The INTERNET permission is required for development. Specifically,
 | 
			
		||||
         the Flutter tool needs it to communicate with the running application
 | 
			
		||||
         to allow setting breakpoints, to provide hot reload, etc.
 | 
			
		||||
    -->
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
</manifest>
 | 
			
		||||
							
								
								
									
										21
									
								
								android/build.gradle.kts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
allprojects {
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
 | 
			
		||||
rootProject.layout.buildDirectory.value(newBuildDir)
 | 
			
		||||
 | 
			
		||||
subprojects {
 | 
			
		||||
    val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
 | 
			
		||||
    project.layout.buildDirectory.value(newSubprojectBuildDir)
 | 
			
		||||
}
 | 
			
		||||
subprojects {
 | 
			
		||||
    project.evaluationDependsOn(":app")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register<Delete>("clean") {
 | 
			
		||||
    delete(rootProject.layout.buildDirectory)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								android/gradle.properties
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
 | 
			
		||||
android.useAndroidX=true
 | 
			
		||||
android.enableJetifier=true
 | 
			
		||||
							
								
								
									
										5
									
								
								android/gradle/wrapper/gradle-wrapper.properties
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
 | 
			
		||||
							
								
								
									
										25
									
								
								android/settings.gradle.kts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
pluginManagement {
 | 
			
		||||
    val flutterSdkPath = run {
 | 
			
		||||
        val properties = java.util.Properties()
 | 
			
		||||
        file("local.properties").inputStream().use { properties.load(it) }
 | 
			
		||||
        val flutterSdkPath = properties.getProperty("flutter.sdk")
 | 
			
		||||
        require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
 | 
			
		||||
        flutterSdkPath
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        gradlePluginPortal()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("dev.flutter.flutter-plugin-loader") version "1.0.0"
 | 
			
		||||
    id("com.android.application") version "8.7.3" apply false
 | 
			
		||||
    id("org.jetbrains.kotlin.android") version "2.1.0" apply false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
include(":app")
 | 
			
		||||
							
								
								
									
										13
									
								
								android_old/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
gradle-wrapper.jar
 | 
			
		||||
/.gradle
 | 
			
		||||
/captures/
 | 
			
		||||
/gradlew
 | 
			
		||||
/gradlew.bat
 | 
			
		||||
/local.properties
 | 
			
		||||
GeneratedPluginRegistrant.java
 | 
			
		||||
 | 
			
		||||
# Remember to never publicly share your keystore.
 | 
			
		||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
 | 
			
		||||
key.properties
 | 
			
		||||
**/*.keystore
 | 
			
		||||
**/*.jks
 | 
			
		||||
							
								
								
									
										58
									
								
								android_old/app/build.gradle
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id "com.android.application"
 | 
			
		||||
    id "kotlin-android"
 | 
			
		||||
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
 | 
			
		||||
    id "dev.flutter.flutter-gradle-plugin"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
def localProperties = new Properties()
 | 
			
		||||
def localPropertiesFile = rootProject.file("local.properties")
 | 
			
		||||
if (localPropertiesFile.exists()) {
 | 
			
		||||
    localPropertiesFile.withReader("UTF-8") { reader ->
 | 
			
		||||
        localProperties.load(reader)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
 | 
			
		||||
if (flutterVersionCode == null) {
 | 
			
		||||
    flutterVersionCode = "1"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
 | 
			
		||||
if (flutterVersionName == null) {
 | 
			
		||||
    flutterVersionName = "1.0"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace = "com.example.hym_ui"
 | 
			
		||||
    compileSdk = flutter.compileSdkVersion
 | 
			
		||||
    ndkVersion = flutter.ndkVersion
 | 
			
		||||
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility = JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility = JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
 | 
			
		||||
        applicationId = "com.example.hym_ui"
 | 
			
		||||
        // You can update the following values to match your application needs.
 | 
			
		||||
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
 | 
			
		||||
        minSdk = flutter.minSdkVersion
 | 
			
		||||
        targetSdk = flutter.targetSdkVersion
 | 
			
		||||
        versionCode = flutterVersionCode.toInteger()
 | 
			
		||||
        versionName = flutterVersionName
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            // TODO: Add your own signing config for the release build.
 | 
			
		||||
            // Signing with the debug keys for now, so `flutter run --release` works.
 | 
			
		||||
            signingConfig = signingConfigs.debug
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
flutter {
 | 
			
		||||
    source = "../.."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								android_old/app/src/debug/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <!-- The INTERNET permission is required for development. Specifically,
 | 
			
		||||
         the Flutter tool needs it to communicate with the running application
 | 
			
		||||
         to allow setting breakpoints, to provide hot reload, etc.
 | 
			
		||||
    -->
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
</manifest>
 | 
			
		||||
							
								
								
									
										45
									
								
								android_old/app/src/main/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <application
 | 
			
		||||
        android:label="hym_ui"
 | 
			
		||||
        android:name="${applicationName}"
 | 
			
		||||
        android:icon="@mipmap/ic_launcher">
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:launchMode="singleTop"
 | 
			
		||||
            android:taskAffinity=""
 | 
			
		||||
            android:theme="@style/LaunchTheme"
 | 
			
		||||
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
 | 
			
		||||
            android:hardwareAccelerated="true"
 | 
			
		||||
            android:windowSoftInputMode="adjustResize">
 | 
			
		||||
            <!-- Specifies an Android theme to apply to this Activity as soon as
 | 
			
		||||
                 the Android process has started. This theme is visible to the user
 | 
			
		||||
                 while the Flutter UI initializes. After that, this theme continues
 | 
			
		||||
                 to determine the Window background behind the Flutter UI. -->
 | 
			
		||||
            <meta-data
 | 
			
		||||
              android:name="io.flutter.embedding.android.NormalTheme"
 | 
			
		||||
              android:resource="@style/NormalTheme"
 | 
			
		||||
              />
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN"/>
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER"/>
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
        <!-- Don't delete the meta-data below.
 | 
			
		||||
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
 | 
			
		||||
        <meta-data
 | 
			
		||||
            android:name="flutterEmbedding"
 | 
			
		||||
            android:value="2" />
 | 
			
		||||
    </application>
 | 
			
		||||
    <!-- Required to query activities that can process text, see:
 | 
			
		||||
         https://developer.android.com/training/package-visibility and
 | 
			
		||||
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
 | 
			
		||||
 | 
			
		||||
         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
 | 
			
		||||
    <queries>
 | 
			
		||||
        <intent>
 | 
			
		||||
            <action android:name="android.intent.action.PROCESS_TEXT"/>
 | 
			
		||||
            <data android:mimeType="text/plain"/>
 | 
			
		||||
        </intent>
 | 
			
		||||
    </queries>
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package com.example.hym_ui
 | 
			
		||||
 | 
			
		||||
import io.flutter.embedding.android.FlutterActivity
 | 
			
		||||
 | 
			
		||||
class MainActivity: FlutterActivity()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Modify this file to customize your launch splash screen -->
 | 
			
		||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <item android:drawable="?android:colorBackground" />
 | 
			
		||||
 | 
			
		||||
    <!-- You can insert your own image assets here -->
 | 
			
		||||
    <!-- <item>
 | 
			
		||||
        <bitmap
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:src="@mipmap/launch_image" />
 | 
			
		||||
    </item> -->
 | 
			
		||||
</layer-list>
 | 
			
		||||
							
								
								
									
										12
									
								
								android_old/app/src/main/res/drawable/launch_background.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Modify this file to customize your launch splash screen -->
 | 
			
		||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <item android:drawable="@android:color/white" />
 | 
			
		||||
 | 
			
		||||
    <!-- You can insert your own image assets here -->
 | 
			
		||||
    <!-- <item>
 | 
			
		||||
        <bitmap
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:src="@mipmap/launch_image" />
 | 
			
		||||
    </item> -->
 | 
			
		||||
</layer-list>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								android_old/app/src/main/res/mipmap-hdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 544 B  | 
							
								
								
									
										
											BIN
										
									
								
								android_old/app/src/main/res/mipmap-mdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 442 B  | 
							
								
								
									
										
											BIN
										
									
								
								android_old/app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 721 B  | 
							
								
								
									
										
											BIN
										
									
								
								android_old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								android_old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										18
									
								
								android_old/app/src/main/res/values-night/styles.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
 | 
			
		||||
    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
 | 
			
		||||
        <!-- Show a splash screen on the activity. Automatically removed when
 | 
			
		||||
             the Flutter engine draws its first frame -->
 | 
			
		||||
        <item name="android:windowBackground">@drawable/launch_background</item>
 | 
			
		||||
    </style>
 | 
			
		||||
    <!-- Theme applied to the Android Window as soon as the process has started.
 | 
			
		||||
         This theme determines the color of the Android Window while your
 | 
			
		||||
         Flutter UI initializes, as well as behind your Flutter UI while its
 | 
			
		||||
         running.
 | 
			
		||||
 | 
			
		||||
         This Theme is only used starting with V2 of Flutter's Android embedding. -->
 | 
			
		||||
    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
 | 
			
		||||
        <item name="android:windowBackground">?android:colorBackground</item>
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										18
									
								
								android_old/app/src/main/res/values/styles.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
 | 
			
		||||
    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
 | 
			
		||||
        <!-- Show a splash screen on the activity. Automatically removed when
 | 
			
		||||
             the Flutter engine draws its first frame -->
 | 
			
		||||
        <item name="android:windowBackground">@drawable/launch_background</item>
 | 
			
		||||
    </style>
 | 
			
		||||
    <!-- Theme applied to the Android Window as soon as the process has started.
 | 
			
		||||
         This theme determines the color of the Android Window while your
 | 
			
		||||
         Flutter UI initializes, as well as behind your Flutter UI while its
 | 
			
		||||
         running.
 | 
			
		||||
 | 
			
		||||
         This Theme is only used starting with V2 of Flutter's Android embedding. -->
 | 
			
		||||
    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
 | 
			
		||||
        <item name="android:windowBackground">?android:colorBackground</item>
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										7
									
								
								android_old/app/src/profile/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <!-- The INTERNET permission is required for development. Specifically,
 | 
			
		||||
         the Flutter tool needs it to communicate with the running application
 | 
			
		||||
         to allow setting breakpoints, to provide hot reload, etc.
 | 
			
		||||
    -->
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
</manifest>
 | 
			
		||||
							
								
								
									
										32
									
								
								android_old/build.gradle
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
buildscript {
 | 
			
		||||
    ext.kotlin_version = '1.9.10'
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
    dependencies {
 | 
			
		||||
        classpath 'com.android.tools.build:gradle:8.3.0'
 | 
			
		||||
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
allprojects {
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rootProject.buildDir = "../build"
 | 
			
		||||
subprojects {
 | 
			
		||||
    project.buildDir = "${rootProject.buildDir}/${project.name}"
 | 
			
		||||
}
 | 
			
		||||
subprojects {
 | 
			
		||||
    project.evaluationDependsOn(":app")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("clean", Delete) {
 | 
			
		||||
    delete rootProject.buildDir
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								android_old/gradle.properties
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
 | 
			
		||||
android.useAndroidX=true
 | 
			
		||||
android.enableJetifier=true
 | 
			
		||||
							
								
								
									
										5
									
								
								android_old/gradle/wrapper/gradle-wrapper.properties
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
 | 
			
		||||
							
								
								
									
										25
									
								
								android_old/settings.gradle
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
pluginManagement {
 | 
			
		||||
    def flutterSdkPath = {
 | 
			
		||||
        def properties = new Properties()
 | 
			
		||||
        file("local.properties").withInputStream { properties.load(it) }
 | 
			
		||||
        def flutterSdkPath = properties.getProperty("flutter.sdk")
 | 
			
		||||
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
 | 
			
		||||
        return flutterSdkPath
 | 
			
		||||
    }()
 | 
			
		||||
 | 
			
		||||
    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        gradlePluginPortal()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
 | 
			
		||||
    id "com.android.application" version "7.3.0" apply false
 | 
			
		||||
    id "org.jetbrains.kotlin.android" version "1.7.10" apply false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
include ":app"
 | 
			
		||||
							
								
								
									
										280
									
								
								lib/Compose.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,280 @@
 | 
			
		|||
import 'package:crab_ui/api_service.dart';
 | 
			
		||||
import 'package:crab_ui/structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:super_editor/super_editor.dart';
 | 
			
		||||
import 'package:super_editor_markdown/super_editor_markdown.dart';
 | 
			
		||||
 | 
			
		||||
class ComposeEmail extends StatefulWidget {
 | 
			
		||||
  final VoidCallback onClose;
 | 
			
		||||
  final Function(String) onMinimize;
 | 
			
		||||
  final Function(String) onSendMessage;
 | 
			
		||||
  GetThreadResponse? emailDraftID;
 | 
			
		||||
  ComposeEmail(
 | 
			
		||||
      {Key? key,
 | 
			
		||||
      required this.onMinimize,
 | 
			
		||||
      required this.onClose,
 | 
			
		||||
      required this.onSendMessage,
 | 
			
		||||
      this.emailDraftID})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _ComposeEmailState createState() => _ComposeEmailState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ComposeEmailState extends State<ComposeEmail> {
 | 
			
		||||
  // if one were to alter a mutableDocument, one should only alter the document through EditRequest to the Editor
 | 
			
		||||
  late final Editor _editor;
 | 
			
		||||
  late final MutableDocument _document;
 | 
			
		||||
  late final MutableDocumentComposer _composer;
 | 
			
		||||
  TextEditingController _emailRecipientController = TextEditingController();
 | 
			
		||||
  TextEditingController _emailSubjectController = TextEditingController();
 | 
			
		||||
  List<String>? contentOfDraft;
 | 
			
		||||
  bool isInitialized = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _loadDraftContent();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _loadDraftContent() async {
 | 
			
		||||
    if (widget.emailDraftID != null) {
 | 
			
		||||
      String? drafted = widget.emailDraftID?.messages.last;
 | 
			
		||||
      if (drafted != null) {
 | 
			
		||||
        contentOfDraft =
 | 
			
		||||
            await ApiService().fetchMarkdownContent([drafted!], "Drafts");
 | 
			
		||||
        setState(() {
 | 
			
		||||
          _document = MutableDocument(nodes: [
 | 
			
		||||
            ParagraphNode(
 | 
			
		||||
              id: Editor.createNodeId(),
 | 
			
		||||
              text: AttributedText(contentOfDraft?[0] ??
 | 
			
		||||
                  ""), // NOW THIS SHOULD BE WTV ITS IN DRAFTS
 | 
			
		||||
            )
 | 
			
		||||
          ]);
 | 
			
		||||
          _composer = MutableDocumentComposer();
 | 
			
		||||
          _editor = createDefaultDocumentEditor(
 | 
			
		||||
              document: _document, composer: _composer);
 | 
			
		||||
          _emailRecipientController.text = widget.emailDraftID!.to[0].address;
 | 
			
		||||
          _emailSubjectController.text = widget.emailDraftID!.subject;
 | 
			
		||||
          isInitialized = true;
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _document = MutableDocument(nodes: [
 | 
			
		||||
          ParagraphNode(
 | 
			
		||||
            id: Editor.createNodeId(),
 | 
			
		||||
            text: AttributedText(""),
 | 
			
		||||
          ),
 | 
			
		||||
        ]);
 | 
			
		||||
        _composer = MutableDocumentComposer();
 | 
			
		||||
        _editor = createDefaultDocumentEditor(
 | 
			
		||||
            document: _document, composer: _composer);
 | 
			
		||||
        isInitialized = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _editor.dispose();
 | 
			
		||||
    _emailRecipientController.dispose();
 | 
			
		||||
    _emailSubjectController.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (!isInitialized) {
 | 
			
		||||
      return Center(
 | 
			
		||||
        child: CircularProgressIndicator(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return Positioned(
 | 
			
		||||
      bottom: 10.0,
 | 
			
		||||
      right: 10.0,
 | 
			
		||||
      child: Material(
 | 
			
		||||
        elevation: 8.0,
 | 
			
		||||
        child: Container(
 | 
			
		||||
            width: 600.0,
 | 
			
		||||
            height: 616.0,
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              color: Colors.white,
 | 
			
		||||
            ),
 | 
			
		||||
            child: Column(children: [
 | 
			
		||||
              AppBar(
 | 
			
		||||
                title: const Text("new message"),
 | 
			
		||||
                actions: [
 | 
			
		||||
                  IconButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        //TODO: implement minimize, and submit email to drafts
 | 
			
		||||
                        widget.onClose();
 | 
			
		||||
                      },
 | 
			
		||||
                      icon: Icon(Icons.minimize, color: Colors.grey[600])),
 | 
			
		||||
                  IconButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        //TODO: implement maximizing the window or widget
 | 
			
		||||
                      },
 | 
			
		||||
                      icon: Icon(Icons.maximize, color: Colors.grey[600])),
 | 
			
		||||
                  IconButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        widget.onClose();
 | 
			
		||||
                      },
 | 
			
		||||
                      icon: Icon(Icons.close, color: Colors.grey[600])),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              Container(
 | 
			
		||||
                // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS
 | 
			
		||||
                // width: 500.0,
 | 
			
		||||
                // height: 40.0,
 | 
			
		||||
                child: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    TextButton(onPressed: () {}, child: Text("To:")),
 | 
			
		||||
                    Expanded(
 | 
			
		||||
                      child: TextField(
 | 
			
		||||
                        controller: _emailRecipientController,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    TextButton(onPressed: () {}, child: Text("Cc")),
 | 
			
		||||
                    SizedBox(
 | 
			
		||||
                      width: 4.0,
 | 
			
		||||
                    ),
 | 
			
		||||
                    TextButton(onPressed: () {}, child: Text("Bcc")),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              SizedBox(
 | 
			
		||||
                height: 4,
 | 
			
		||||
              ),
 | 
			
		||||
              Container(
 | 
			
		||||
                // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS
 | 
			
		||||
                width: 500.0,
 | 
			
		||||
                // height: 40.0,
 | 
			
		||||
                child: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Expanded(
 | 
			
		||||
                      child: TextField(
 | 
			
		||||
                        controller: _emailSubjectController,
 | 
			
		||||
                        decoration: InputDecoration(
 | 
			
		||||
                          hintText: "Subject",
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              Expanded(
 | 
			
		||||
                //here the widget goes
 | 
			
		||||
                child: SuperEditor(
 | 
			
		||||
                  //make this its own
 | 
			
		||||
                  editor: _editor,
 | 
			
		||||
                  plugins: {MarkdownInlineUpstreamSyntaxPlugin()},
 | 
			
		||||
 | 
			
		||||
                  // stylesheet: Stylesheet(
 | 
			
		||||
                  //   rules: [StyleRule(BlockSelector.all, (doc, docNode) {
 | 
			
		||||
                  //     return {
 | 
			
		||||
                  //       Styles.maxWidth: 640.0,
 | 
			
		||||
                  //       Styles.padding: const CascadingPadding.symmetric(horizontal: 24),
 | 
			
		||||
                  //       Styles.textStyle: const TextStyle(
 | 
			
		||||
                  //         color: Colors.black,
 | 
			
		||||
                  //         // fontSize: 15,
 | 
			
		||||
                  //         height: 1.4,
 | 
			
		||||
                  //       ),
 | 
			
		||||
                  //     };
 | 
			
		||||
                  //   }),],
 | 
			
		||||
                  // inlineTextStyler: defaultInlineTextStyler)
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              Container(
 | 
			
		||||
                padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
 | 
			
		||||
                child: Row(
 | 
			
		||||
                  mainAxisAlignment: MainAxisAlignment.start,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    ElevatedButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        print('sent');
 | 
			
		||||
                        String markdown =
 | 
			
		||||
                            serializeDocumentToMarkdown(_editor.document);
 | 
			
		||||
 | 
			
		||||
                        print(_emailRecipientController.text);
 | 
			
		||||
                        print(_emailSubjectController.text);
 | 
			
		||||
                        print(markdown);
 | 
			
		||||
                        ApiService().sendEmail(_emailRecipientController.text,
 | 
			
		||||
                            _emailSubjectController.text, markdown);
 | 
			
		||||
                      },
 | 
			
		||||
                      style: ElevatedButton.styleFrom(
 | 
			
		||||
                          shape: RoundedRectangleBorder(
 | 
			
		||||
                              borderRadius: BorderRadius.circular(30)),
 | 
			
		||||
                          elevation: 4,
 | 
			
		||||
                          foregroundColor: Colors.white,
 | 
			
		||||
                          backgroundColor: Colors.blueAccent),
 | 
			
		||||
                      child: Row(
 | 
			
		||||
                        mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                        children: [
 | 
			
		||||
                          const Text(
 | 
			
		||||
                            'Send',
 | 
			
		||||
                            style: TextStyle(fontWeight: FontWeight.bold),
 | 
			
		||||
                          ),
 | 
			
		||||
                          // const SizedBox(
 | 
			
		||||
                          //   width: 8,
 | 
			
		||||
                          // ),
 | 
			
		||||
                          // Container(
 | 
			
		||||
                          //     height: 30, width: 1.0, color: Colors.white),
 | 
			
		||||
                          // const SizedBox(
 | 
			
		||||
                          //   width: 8,
 | 
			
		||||
                          // ),
 | 
			
		||||
                          // const Icon(
 | 
			
		||||
                          //   Icons.arrow_drop_down,
 | 
			
		||||
                          //   size: 24,
 | 
			
		||||
                          // )
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              )
 | 
			
		||||
            ])),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class OverlayService {
 | 
			
		||||
  static final OverlayService _instance = OverlayService._internal();
 | 
			
		||||
  factory OverlayService() => _instance;
 | 
			
		||||
  OverlayService._internal();
 | 
			
		||||
  OverlayEntry? _overlayEntry;
 | 
			
		||||
  GetThreadResponse? draftID;
 | 
			
		||||
 | 
			
		||||
  void showPersistentWidget(BuildContext context) {
 | 
			
		||||
    if (_overlayEntry != null) {
 | 
			
		||||
      print("overlay visible");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    _overlayEntry = OverlayEntry(
 | 
			
		||||
        builder: (context) => ComposeEmail(
 | 
			
		||||
            onClose: () {
 | 
			
		||||
              removeComposeWidget();
 | 
			
		||||
            },
 | 
			
		||||
            onMinimize: (String content) {
 | 
			
		||||
              minimizeComposeWidget(content);
 | 
			
		||||
            },
 | 
			
		||||
            onSendMessage: (message) {
 | 
			
		||||
              print('msg senf form overlay $message');
 | 
			
		||||
            },
 | 
			
		||||
            emailDraftID: draftID));
 | 
			
		||||
    Navigator.of(context).overlay?.insert(_overlayEntry!);
 | 
			
		||||
    print("inserted into tree");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void removeComposeWidget() {
 | 
			
		||||
    _overlayEntry?.remove();
 | 
			
		||||
    _overlayEntry = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String minimizeComposeWidget(String content) {
 | 
			
		||||
    //just hide the overlay but keep its info
 | 
			
		||||
    return '';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								lib/SonicEmailViewAndroid.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class SonicEmailView extends StatefulWidget {
 | 
			
		||||
  SerializableMessage email;
 | 
			
		||||
  String emailHTML;
 | 
			
		||||
 | 
			
		||||
  SonicEmailView({required this.email, required this.emailHTML});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _SonicEmailViewState createState() => _SonicEmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SonicEmailViewState extends State<SonicEmailView> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(body: Text("sonic email android"));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								lib/SonicEmailViewStub.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class SonicEmailView extends StatefulWidget {
 | 
			
		||||
  SerializableMessage email;
 | 
			
		||||
  String emailHTML;
 | 
			
		||||
 | 
			
		||||
  SonicEmailView({required this.email, required this.emailHTML});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _SonicEmailViewState createState() => _SonicEmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SonicEmailViewState extends State<SonicEmailView> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
    body:Text("sonic email stub")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								lib/SonicEmailViewWeb.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
import 'package:crab_ui/augment.dart';
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'dart:ui_web' as ui;
 | 
			
		||||
import 'dart:js_interop';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class SonicEmailView extends StatefulWidget {
 | 
			
		||||
  SerializableMessage email;
 | 
			
		||||
  String emailHTML;
 | 
			
		||||
 | 
			
		||||
  SonicEmailView({required this.email, required this.emailHTML});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _SonicEmailViewState createState() => _SonicEmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SonicEmailViewState extends State<SonicEmailView> {
 | 
			
		||||
  String viewTypeIDs = "";
 | 
			
		||||
  int heightOFViewtype = 0;
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
 | 
			
		||||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    AugmentClasses.handleJump(spanId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleViewspecs(String queryViewspecs) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleFiltering(String query) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _init() async {
 | 
			
		||||
    await _registerViewFactory(widget.emailHTML);
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _registerViewFactory(String currentContent) async {
 | 
			
		||||
    // setState(() { //update to do item per item
 | 
			
		||||
    // each item to have itsviewtype ID
 | 
			
		||||
    // is this necessarey here??
 | 
			
		||||
 | 
			
		||||
    //could just move to collapsable
 | 
			
		||||
 | 
			
		||||
    // for (var emailHTML in widget.threadHTML) {
 | 
			
		||||
    String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
 | 
			
		||||
 | 
			
		||||
    final ghost = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
      ..style.visibility = 'hidden'
 | 
			
		||||
      ..style.position = 'absolute'
 | 
			
		||||
      ..style.width = '100%'
 | 
			
		||||
      ..style.overflow = 'auto'
 | 
			
		||||
      ..innerHTML = currentContent.toJS;
 | 
			
		||||
    web.document.body?.append(ghost);
 | 
			
		||||
    await Future.delayed(Duration(milliseconds: 10));
 | 
			
		||||
 | 
			
		||||
    final heightOfEmail = ghost.scrollHeight;
 | 
			
		||||
    ghost.remove();
 | 
			
		||||
 | 
			
		||||
    final HTMLsnippet = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
      ..id = viewTypeId
 | 
			
		||||
      ..innerHTML = widget
 | 
			
		||||
          .emailHTML.toJS; // temporarily index because it has to do all of them
 | 
			
		||||
    HTMLsnippet.style
 | 
			
		||||
      ..width = '100%'
 | 
			
		||||
      ..height = '${heightOfEmail}px'
 | 
			
		||||
      ..overflow = 'auto'
 | 
			
		||||
      ..scrollBehavior = 'smooth';
 | 
			
		||||
 | 
			
		||||
    ui.platformViewRegistry.registerViewFactory(
 | 
			
		||||
      viewTypeId,
 | 
			
		||||
      (int viewId) => HTMLsnippet,
 | 
			
		||||
    );
 | 
			
		||||
    this.viewTypeIDs = viewTypeId;
 | 
			
		||||
    this.heightOFViewtype = heightOfEmail;
 | 
			
		||||
    print(viewTypeIDs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return _isLoaded
 | 
			
		||||
        ? Scaffold(
 | 
			
		||||
            appBar: AppBar(title: Text(widget.email.subject)),
 | 
			
		||||
            body: Stack(
 | 
			
		||||
              children: [
 | 
			
		||||
                Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    EmailToolbar(
 | 
			
		||||
                      onButtonPressed: () => {},
 | 
			
		||||
                      onJumpToNumbering: _scrollToNumber,
 | 
			
		||||
                      onViewspecs: _handleViewspecs,
 | 
			
		||||
                      onFiltering: _handleFiltering,
 | 
			
		||||
                      emails: [widget.email.name], subject: '', rootAugment: null,
 | 
			
		||||
                    ),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      // title of email
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          widget.email.subject,
 | 
			
		||||
                          style: TextStyle(fontSize: 30),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          'from ${widget.email.name}',
 | 
			
		||||
                          style: TextStyle(fontSize: 18),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Text(
 | 
			
		||||
                          '<${widget.email.from}>',
 | 
			
		||||
                          style: TextStyle(fontSize: 18),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Spacer(),
 | 
			
		||||
                        Text(
 | 
			
		||||
                          '${widget.email.date}',
 | 
			
		||||
                          textAlign: TextAlign.right,
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    // TODO: make a case where if one of these is the user's email it just says me :)))))
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          'to ${widget.email.to.toString()}',
 | 
			
		||||
                          style: TextStyle(fontSize: 15),
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    Expanded(
 | 
			
		||||
                        // child: SizedBox(
 | 
			
		||||
                        // height: heightOFViewtype.toDouble(),
 | 
			
		||||
                        child: HtmlElementView(
 | 
			
		||||
                      key: UniqueKey(), viewType: this.viewTypeIDs,
 | 
			
		||||
                      // ),
 | 
			
		||||
                    ))
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
        : const Center(
 | 
			
		||||
            child: CircularProgressIndicator(),
 | 
			
		||||
          );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,26 +1,16 @@
 | 
			
		|||
// this file should handle most of the API calls
 | 
			
		||||
// it also builds some widgets, but it will be modulated later
 | 
			
		||||
// it also builds some widgets, but it will be modulated later // chat it did
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
 | 
			
		||||
import 'collapsableEmails.dart';
 | 
			
		||||
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:ui_web' as ui;
 | 
			
		||||
import 'augment.dart';
 | 
			
		||||
// import 'dart:html' as html;
 | 
			
		||||
// import 'dart:js' as js;
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'dart:js_interop' as js;
 | 
			
		||||
 | 
			
		||||
class ApiService {
 | 
			
		||||
  static String ip = "";
 | 
			
		||||
  static String port = "";
 | 
			
		||||
  static String ip = '127.0.0.1';
 | 
			
		||||
  static String port = "3001";
 | 
			
		||||
  static List<AttachmentResponse> threadAttachments =
 | 
			
		||||
      []; //holds attachments of the thread
 | 
			
		||||
  static String currFolder = "";
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +50,40 @@ class ApiService {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<GetThreadResponse>> fetchEmailsFromFolderReversed(
 | 
			
		||||
      String folder, int pagenitaion) async {
 | 
			
		||||
    try {
 | 
			
		||||
      var url = Uri.http('$ip:$port', 'sorted_threads_by_date_current', {
 | 
			
		||||
        'folder': folder,
 | 
			
		||||
        'limit': '50',
 | 
			
		||||
        'offset': pagenitaion.toString(),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      var response = await http.get(url);
 | 
			
		||||
      List<GetThreadResponse> allEmails = [];
 | 
			
		||||
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        List json = jsonDecode(response.body);
 | 
			
		||||
        for (var item in json) {
 | 
			
		||||
          //each item in the json is a date
 | 
			
		||||
          if (item.length > 1 && item[0] is String && item[1] is List) {
 | 
			
		||||
            List<int> threadIDs = List<int>.from(item[1]);
 | 
			
		||||
            for (var threadId in threadIDs) {
 | 
			
		||||
              await fetchThreads(threadId, allEmails);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        currFolder = folder;
 | 
			
		||||
        return allEmails;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw Exception('Failed to load threads');
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('_displayEmailsFromFolder caught error: $e');
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> fetchThreads(
 | 
			
		||||
      //populates allEmails, which is the List that contains all the emails in a thread
 | 
			
		||||
      int threadId,
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +172,6 @@ class ApiService {
 | 
			
		|||
    } catch (e) {
 | 
			
		||||
      print('_getEmailContent caught error: $e');
 | 
			
		||||
    }
 | 
			
		||||
    // return content;
 | 
			
		||||
    return HTMLofThread;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +203,7 @@ class ApiService {
 | 
			
		|||
  Future<bool> moveEmail(
 | 
			
		||||
      //only moves the first email of the thread //or perhaps should do the last
 | 
			
		||||
      String fromFolder,
 | 
			
		||||
      String thread_id,
 | 
			
		||||
      String thread_id, //uid
 | 
			
		||||
      String toFolder) async {
 | 
			
		||||
    var url = Uri.http('$ip:$port', 'move_email');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -191,27 +214,28 @@ class ApiService {
 | 
			
		|||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SerializableMessage firstMail = mailsInSerializable[0];
 | 
			
		||||
 | 
			
		||||
    Map<String, String> requestBody = {
 | 
			
		||||
      'from': fromFolder,
 | 
			
		||||
      'uid': firstMail.uid.toString(),
 | 
			
		||||
      'to': toFolder,
 | 
			
		||||
    };
 | 
			
		||||
    // SerializableMessage firstMail = mailsInSerializable[0];
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      var response = await http.post(
 | 
			
		||||
        url,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: jsonEncode(requestBody),
 | 
			
		||||
      );
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        print('response body ${response.body}');
 | 
			
		||||
        return true;
 | 
			
		||||
      } else {
 | 
			
		||||
        print('error ${response.statusCode} ${response.body}');
 | 
			
		||||
      for (SerializableMessage mail in mailsInSerializable) {
 | 
			
		||||
        Map<String, String> requestBody = {
 | 
			
		||||
          'from': fromFolder,
 | 
			
		||||
          'uid': mail.uid.toString(),
 | 
			
		||||
          'to': "Deleted Crabmail",
 | 
			
		||||
        };
 | 
			
		||||
        var response = await http.post(
 | 
			
		||||
          url,
 | 
			
		||||
          headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: jsonEncode(requestBody),
 | 
			
		||||
        );
 | 
			
		||||
        if (response.statusCode == 200) {
 | 
			
		||||
          print('response body ${response.body}');
 | 
			
		||||
          return true;
 | 
			
		||||
        } else {
 | 
			
		||||
          print('error ${response.statusCode} ${response.body}');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("failed trying to post move_email, with error: $e");
 | 
			
		||||
| 
						 | 
				
			
			@ -229,6 +253,17 @@ class ApiService {
 | 
			
		|||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  Future<List<String>> fetchContacts() async {
 | 
			
		||||
    try {
 | 
			
		||||
      var url = Uri.http('$ip:$port', 'get_contacts');
 | 
			
		||||
      var response = await http.get(url);
 | 
			
		||||
      return List<String>.from(json.decode(response.body));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('fetchFolders caught error: $e');
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> createFolder(String folderName) async {
 | 
			
		||||
    var url = Uri.http('$ip:$port', 'create_folder');
 | 
			
		||||
| 
						 | 
				
			
			@ -351,308 +386,145 @@ class ApiService {
 | 
			
		|||
    return AttachmentResponse(name: "error", data: Uint8List(0));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //TODO: MOVE THIS INTO WEB
 | 
			
		||||
//   Future<List<Map<String, dynamic>>> getMarkerPosition() async {
 | 
			
		||||
//     //this is so we can put a widget right below each email, but the way how the email content is generated
 | 
			
		||||
//     //leads to problems as for a) the html is added one right after the other in one iframe, b)
 | 
			
		||||
//     // if it was multiple iframes then the scrolling to jump would not work as expected
 | 
			
		||||
  Future<List<String>> fetchMarkdownContent(
 | 
			
		||||
      List<String> IDsString, String emailFolder) async {
 | 
			
		||||
    List<String> MDofThread = [];
 | 
			
		||||
    threadAttachments = [];
 | 
			
		||||
    int counter = 0;
 | 
			
		||||
 | 
			
		||||
//     print("marker called");
 | 
			
		||||
//     // JavaScript code embedded as a string
 | 
			
		||||
//     String jsCode = '''
 | 
			
		||||
// (async function waitForIframeAndMarkers() {
 | 
			
		||||
//   try {
 | 
			
		||||
//     return await new Promise((resolve) => {
 | 
			
		||||
//       const interval = setInterval(() => {
 | 
			
		||||
//         console.log("⏳ Checking for iframe...");
 | 
			
		||||
//         var iframe = document.getElementsByTagName('iframe')[0];
 | 
			
		||||
//         if (iframe && iframe.contentDocument) {
 | 
			
		||||
//           console.log("✅ Iframe found!");
 | 
			
		||||
//           var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
 | 
			
		||||
//           var markers = iframeDoc.querySelectorAll('[id^="JuanBedarramarker"]');
 | 
			
		||||
//           if (markers.length > 0) {
 | 
			
		||||
//             console.log(`✅ Found markers in the iframe.`);
 | 
			
		||||
//             var positions = [];
 | 
			
		||||
//             markers.forEach((marker) => {
 | 
			
		||||
//               var rect = marker.getBoundingClientRect();
 | 
			
		||||
//               positions.push({
 | 
			
		||||
//                 id: marker.id,
 | 
			
		||||
//                 x: rect.left + window.scrollX,
 | 
			
		||||
//                 y: rect.top + window.scrollY,
 | 
			
		||||
//               });
 | 
			
		||||
//             });
 | 
			
		||||
//             console.log("📌 Marker positions:", positions);
 | 
			
		||||
//             clearInterval(interval);
 | 
			
		||||
//             resolve(JSON.stringify(positions)); // Ensure proper JSON string
 | 
			
		||||
//           } else {
 | 
			
		||||
//             console.log("❌ No markers found yet.");
 | 
			
		||||
//           }
 | 
			
		||||
//         } else {
 | 
			
		||||
//           console.log("❌ Iframe not found or not loaded yet.");
 | 
			
		||||
//         }
 | 
			
		||||
//       }, 200);
 | 
			
		||||
//     });
 | 
			
		||||
//   } catch (error) {
 | 
			
		||||
//     console.error("JS Error:", error);
 | 
			
		||||
//     throw error; // Propagate error to Dart
 | 
			
		||||
//   }
 | 
			
		||||
// })();
 | 
			
		||||
// ''';
 | 
			
		||||
    try {
 | 
			
		||||
      //attaches email after email from a thread
 | 
			
		||||
      for (var id in IDsString) {
 | 
			
		||||
        var url = Uri.http('$ip:$port', 'email_md', {'id': id});
 | 
			
		||||
        print(url);
 | 
			
		||||
        var response = await http.get(url);
 | 
			
		||||
        currThread.add(id);
 | 
			
		||||
        if (response.statusCode == 200) {
 | 
			
		||||
          counter += 1;
 | 
			
		||||
          Map<String, dynamic> json = jsonDecode(response.body);
 | 
			
		||||
 | 
			
		||||
//     try {
 | 
			
		||||
//       // Execute the JavaScript code using eval
 | 
			
		||||
//       // final result = await js.context.callMethod('eval', [jsCode]);
 | 
			
		||||
 | 
			
		||||
//       if (result != null && result is String) {
 | 
			
		||||
//         print("Result received: $result");
 | 
			
		||||
 | 
			
		||||
//         // Parse the JSON string returned by JavaScript into a Dart list of maps
 | 
			
		||||
//         final List<dynamic> parsedResult = jsonDecode(result);
 | 
			
		||||
//         var positions = List<Map<String, dynamic>>.from(parsedResult);
 | 
			
		||||
//         print("positions put on");
 | 
			
		||||
//         print(positions);
 | 
			
		||||
//         return positions;
 | 
			
		||||
//       } else {
 | 
			
		||||
//         print("result is null or not a string");
 | 
			
		||||
//       }
 | 
			
		||||
//     } catch (e, stackTrace) {
 | 
			
		||||
//       print("Error executing JavaScript: $e");
 | 
			
		||||
//       print(stackTrace);
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     return [];
 | 
			
		||||
//   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EmailView extends StatefulWidget {
 | 
			
		||||
  final List<String> emailContent;
 | 
			
		||||
  final String from;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String to;
 | 
			
		||||
  final String subject;
 | 
			
		||||
  final String date;
 | 
			
		||||
  final String id;
 | 
			
		||||
  final List<String> messages;
 | 
			
		||||
 | 
			
		||||
  const EmailView({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.emailContent,
 | 
			
		||||
    required this.from,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.to,
 | 
			
		||||
    required this.subject,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.messages,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _EmailViewState createState() => _EmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmailViewState extends State<EmailView> {
 | 
			
		||||
  //html css rendering thing
 | 
			
		||||
  late Key iframeKey;
 | 
			
		||||
  late String currentContent;
 | 
			
		||||
  late String viewTypeId; //make this a list too???
 | 
			
		||||
  Future<List<Map<String, dynamic>>>? _markerPositionsFuture;
 | 
			
		||||
  // TextEditingController _jumpController = TextEditingController();
 | 
			
		||||
  final hardcodedMarkers = [
 | 
			
		||||
    {'id': 'marker1', 'x': 50, 'y': 100},
 | 
			
		||||
    {'id': 'marker2', 'x': 150, 'y': 200},
 | 
			
		||||
    {'id': 'marker3', 'x': 250, 'y': 300},
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    print("thread id? ${widget.id}");
 | 
			
		||||
    List<String> currentContent = widget
 | 
			
		||||
        .emailContent; //html of the email/ actually entire thread, gives me little space to play in between
 | 
			
		||||
    // i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse
 | 
			
		||||
    // _registerViewFactory(currentContent);
 | 
			
		||||
          MDofThread.add(json['md'] ?? '');
 | 
			
		||||
          try {
 | 
			
		||||
            List<AttachmentInfo> attachments =
 | 
			
		||||
                await getAttachmentsInfo(emailFolder, id);
 | 
			
		||||
            for (var attachment in attachments) {
 | 
			
		||||
              //TODO: for each attachment creaate at the bottom a widget for each individual one
 | 
			
		||||
              threadAttachments
 | 
			
		||||
                  .add(await getAttachment(emailFolder, id, attachment.name));
 | 
			
		||||
            }
 | 
			
		||||
          } catch (innerError) {
 | 
			
		||||
            print('_getAttachment info caught error $innerError');
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('_getMDContent caught error: $e');
 | 
			
		||||
    }
 | 
			
		||||
    // print("IDS inside fetch md content $IDsString");
 | 
			
		||||
    // print("inside apiservice $MDofThread");
 | 
			
		||||
    return MDofThread;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // void _registerViewFactory(List<String> currentContent) { // i think this doesnt work anymore
 | 
			
		||||
  //   setState(() { //update to do item per item
 | 
			
		||||
  //   // each item to have itsviewtype ID
 | 
			
		||||
  // // is this necessarey here??
 | 
			
		||||
 | 
			
		||||
  // //could just move to collapsable
 | 
			
		||||
 | 
			
		||||
  //     viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
 | 
			
		||||
  //     final emailHTML = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
  //       ..id = viewTypeId
 | 
			
		||||
  //       ..innerHTML = currentContent[0].toJS; // temporarily index because it has to do all of them
 | 
			
		||||
  //     emailHTML.style
 | 
			
		||||
  //       ..width = '100%'
 | 
			
		||||
  //       ..height = '100%'
 | 
			
		||||
  //       ..overflow = 'auto'
 | 
			
		||||
  //       ..scrollBehavior = 'smooth';
 | 
			
		||||
 | 
			
		||||
  //     ui.platformViewRegistry.registerViewFactory(
 | 
			
		||||
  //       viewTypeId,
 | 
			
		||||
  //       (int viewId) => emailHTML,
 | 
			
		||||
  //     );
 | 
			
		||||
  //   });
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    AugmentClasses.handleJump(spanId);
 | 
			
		||||
  Future<void> markAsSeen(int thread_id) async {
 | 
			
		||||
    try {
 | 
			
		||||
      var url = Uri.http(
 | 
			
		||||
          '$ip:$port', 'post_seen_thread', {'id': thread_id.toString()});
 | 
			
		||||
      print("url: $url");
 | 
			
		||||
      var response = await http.get(url);
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        var result = response.body;
 | 
			
		||||
        print("data $result");
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("markAsSeen failed $e");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: void _invisibility(String )  //to make purple numbers not visible
 | 
			
		||||
  Future<void> markAsUnseen(int thread_id) async {
 | 
			
		||||
    try {
 | 
			
		||||
      var url = Uri.http(
 | 
			
		||||
          '$ip:$port', 'post_unseen_thread', {'id': thread_id.toString()});
 | 
			
		||||
      print("url: $url");
 | 
			
		||||
      var response = await http.get(url);
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        var result = response.body;
 | 
			
		||||
        print("data $result");
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("markAsUnseen failed $e");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    // print("thread id ${widget.id}");
 | 
			
		||||
    ApiService.currThreadID = widget.id;
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(widget.name),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Stack(
 | 
			
		||||
          children: [
 | 
			
		||||
            Column(
 | 
			
		||||
              children: [
 | 
			
		||||
                EmailToolbar(
 | 
			
		||||
                  onJumpToSpan: _scrollToNumber,
 | 
			
		||||
                  onButtonPressed: () => {},
 | 
			
		||||
                  // AugmentClasses.handleJump(viewTypeId, '1');
 | 
			
		||||
                  // print("button got pressed?");
 | 
			
		||||
  Future<bool> deleteEmail(String from_folder, int thread_id) async {
 | 
			
		||||
    // post
 | 
			
		||||
    try {
 | 
			
		||||
      List<SerializableMessage> mailsInSerializable =
 | 
			
		||||
          await this.threadsInSerializable(thread_id.toString());
 | 
			
		||||
 | 
			
		||||
                  //   _registerViewFactory(r"""
 | 
			
		||||
                  // <h1>Welcome to My Website</h1>
 | 
			
		||||
                  // <p>This is a simple HTML page.</p>
 | 
			
		||||
                  // <h2>What is HTML?</h2>
 | 
			
		||||
                  // <p>HTML (HyperText Markup Language) is the most basic building~ block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
 | 
			
		||||
                  // <h3>Here's a simple list:</h3>
 | 
			
		||||
                  // <ul>
 | 
			
		||||
                  //     <li>HTML elements are the building blocks of HTML pages</li>
 | 
			
		||||
                  //     <li>HTML uses tags like <code><tag></code> to organize and format content</li>
 | 
			
		||||
                  //     <li>CSS is used with HTML to style pages</li>
 | 
			
		||||
                  // </ul>
 | 
			
		||||
                  // <p>Copyright © 2023</p>
 | 
			
		||||
                  // """);
 | 
			
		||||
                  //   print("change");
 | 
			
		||||
                  // widget.emailContent = r"
 | 
			
		||||
      if (mailsInSerializable.isEmpty) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      Map<String, String> requestBody = {
 | 
			
		||||
        "from": from_folder,
 | 
			
		||||
        "uid": mailsInSerializable.first.uid.toString(),
 | 
			
		||||
        "to": "not used"
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
                  //
 | 
			
		||||
                ),
 | 
			
		||||
                Row(
 | 
			
		||||
                  // title of email
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      widget.subject,
 | 
			
		||||
                      style: TextStyle(fontSize: 30),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      'from ${widget.name}',
 | 
			
		||||
                      style: TextStyle(fontSize: 18),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Text(
 | 
			
		||||
                      '<${widget.from}>',
 | 
			
		||||
                      style: TextStyle(fontSize: 18),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Spacer(),
 | 
			
		||||
                    Text(
 | 
			
		||||
                      '${widget.date}',
 | 
			
		||||
                      textAlign: TextAlign.right,
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                // TODO: make a case where if one of these is the user's email it just says me :)))))
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      'to ${widget.to.toString()}',
 | 
			
		||||
                      style: TextStyle(fontSize: 15),
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: CollapsableEmails(
 | 
			
		||||
                    //change here
 | 
			
		||||
                    thread: widget.messages,  //this wont work in serializable
 | 
			
		||||
                    threadHTML: widget.emailContent,
 | 
			
		||||
                    threadIDs: widget.id,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                // Expanded(
 | 
			
		||||
                //   child: HtmlElementView(
 | 
			
		||||
                //     key: UniqueKey(),
 | 
			
		||||
                //     viewType: viewTypeId,
 | 
			
		||||
                //   ),
 | 
			
		||||
                // ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
      //delete the email that is given to the
 | 
			
		||||
      var url = Uri.http("$ip:$port", 'delete_email');
 | 
			
		||||
      var response = await http.post(url,
 | 
			
		||||
          headers: {
 | 
			
		||||
            "Content-Type": "application/json",
 | 
			
		||||
          },
 | 
			
		||||
          body: jsonEncode(requestBody));
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        print("response body: ${response.body}");
 | 
			
		||||
        return true;
 | 
			
		||||
      } else {
 | 
			
		||||
        print("not 200: ${response.body}");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("error in deleteEmail $e");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
            // Overlay widgets dynamically based on marker positions
 | 
			
		||||
            // FutureBuilder<List<Map<String, dynamic>>>(
 | 
			
		||||
            //   future: _markerPositionsFuture,
 | 
			
		||||
            //   builder: (context, snapshot) {
 | 
			
		||||
            //     print("FutureBuilder state: ${snapshot.connectionState}");
 | 
			
		||||
            //     if (snapshot.connectionState == ConnectionState.waiting) {
 | 
			
		||||
            //       return Center(child: CircularProgressIndicator());
 | 
			
		||||
            //     }
 | 
			
		||||
            //     if (snapshot.hasError) {
 | 
			
		||||
            //       print("Error in FutureBuilder: ${snapshot.error}");
 | 
			
		||||
            //       return Center(child: Text('error loading markers'));
 | 
			
		||||
            //     }
 | 
			
		||||
            //     if (snapshot.hasData && snapshot.data != null) {
 | 
			
		||||
            //       final markers = snapshot.data!;
 | 
			
		||||
            //       return Stack(
 | 
			
		||||
            //         children: markers.map((marker) {
 | 
			
		||||
            //           return Positioned(
 | 
			
		||||
            //             left: marker['x'].toDouble(),
 | 
			
		||||
            //             top: marker['y'].toDouble(),
 | 
			
		||||
            //             child: GestureDetector(
 | 
			
		||||
            //               onTap: () {
 | 
			
		||||
            //                 print('Tapped on ${marker['id']}');
 | 
			
		||||
            //               },
 | 
			
		||||
            //               child: Container(
 | 
			
		||||
            //                 width: 50,
 | 
			
		||||
            //                 height: 50,
 | 
			
		||||
            //                 color: Colors.red,
 | 
			
		||||
            //                 child: Center(
 | 
			
		||||
            //                   child: Text(
 | 
			
		||||
            //                     marker['id'],
 | 
			
		||||
            //                     style: TextStyle(color: Colors.white),
 | 
			
		||||
            //                   ),
 | 
			
		||||
            //                 ),
 | 
			
		||||
            //               ),
 | 
			
		||||
            //             ),
 | 
			
		||||
            //           );
 | 
			
		||||
            //         }).toList(),
 | 
			
		||||
            //       );
 | 
			
		||||
            //     }
 | 
			
		||||
  Future<bool> sendEmail(
 | 
			
		||||
      String? to, String? subject, String? emailContent) async {
 | 
			
		||||
    try {
 | 
			
		||||
      var url = Uri.http('$ip:$port', 'send_email');
 | 
			
		||||
 | 
			
		||||
            //     return SizedBox.shrink(); // No markers found
 | 
			
		||||
            //   },
 | 
			
		||||
            // ),
 | 
			
		||||
            // Red widget overlay
 | 
			
		||||
            // Positioned(
 | 
			
		||||
            //   left: 8, // Adjust based on your desired position
 | 
			
		||||
            //   top: 100 + 44 + 5, // Adjust based on your desired position
 | 
			
		||||
            //   child: IgnorePointer(
 | 
			
		||||
            //     ignoring: true, // Ensures the iframe remains interactive
 | 
			
		||||
            //     child: Container(
 | 
			
		||||
            //       color: Colors.red,
 | 
			
		||||
            //       width: 100,
 | 
			
		||||
            //       height: 50,
 | 
			
		||||
            //       child: Center(
 | 
			
		||||
            //         child: Text(
 | 
			
		||||
            //           'Overlay',
 | 
			
		||||
            //           style: TextStyle(color: Colors.white),
 | 
			
		||||
            //         ),
 | 
			
		||||
            //       ),
 | 
			
		||||
            //     ),
 | 
			
		||||
            //   ),
 | 
			
		||||
            // ),
 | 
			
		||||
          ],
 | 
			
		||||
        ));
 | 
			
		||||
      Map<String, dynamic> requestBody = {
 | 
			
		||||
        "to": [to ?? ""],
 | 
			
		||||
        "cc": [],
 | 
			
		||||
        "bcc": [],
 | 
			
		||||
        "subject": subject ?? "Untitled",
 | 
			
		||||
        "in_reply_to": "",
 | 
			
		||||
        "messages": [
 | 
			
		||||
          {"message": emailContent ?? "", "is_html": false}
 | 
			
		||||
        ],
 | 
			
		||||
        "attachments": [],
 | 
			
		||||
        "inline_images": [],
 | 
			
		||||
      };
 | 
			
		||||
      var response = await http.post(
 | 
			
		||||
        url,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: jsonEncode(requestBody),
 | 
			
		||||
      );
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        print("response body: ${response.body}");
 | 
			
		||||
      } else {
 | 
			
		||||
        print('error: ${response.statusCode}, response body: ${response.body}');
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("error in post send email $e");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								lib/attachamentDownloadStub.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
import 'structs.dart';
 | 
			
		||||
 | 
			
		||||
class Attachmentdownload {
 | 
			
		||||
  Future<void> saveFile(AttachmentResponse attachment) async {
 | 
			
		||||
    print("stub attachment download");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +1,3 @@
 | 
			
		|||
import 'dart:html' as html;
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:file_saver/file_saver.dart';
 | 
			
		||||
 | 
			
		||||
class Attachmentdownload {
 | 
			
		||||
  Future<void> saveFile(AttachmentResponse attachment) async {
 | 
			
		||||
    await FileSaver.instance.saveFile(
 | 
			
		||||
      name: attachment.name.toString().substring(0, attachment.name.toString().lastIndexOf('.')),
 | 
			
		||||
      bytes: attachment.data,
 | 
			
		||||
      ext: attachment.name.toString().substring(attachment.name.toString().lastIndexOf('.')+1)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export 'attachamentDownloadStub.dart'
 | 
			
		||||
    if (dart.library.io) 'attachmentDownloadAndroid.dart';
 | 
			
		||||
  // if (dart.library.js_interop) 'attachmentDownloadWeb.dart';
 | 
			
		||||
							
								
								
									
										7
									
								
								lib/attachmentDownloadAndroid.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
import 'structs.dart';
 | 
			
		||||
 | 
			
		||||
class Attachmentdownload {
 | 
			
		||||
  Future<void> saveFile(AttachmentResponse attachment) async {
 | 
			
		||||
    print("android attachment download");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								lib/attachmentDownloadWeb.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
// import 'structs.dart';
 | 
			
		||||
// import 'package:file_saver/file_saver.dart';
 | 
			
		||||
 | 
			
		||||
// class Attachmentdownload {
 | 
			
		||||
//   Future<void> saveFile(AttachmentResponse attachment) async {
 | 
			
		||||
//     await FileSaver.instance.saveFile(
 | 
			
		||||
//       name: attachment.name.toString().substring(0, attachment.name.toString().lastIndexOf('.')),
 | 
			
		||||
//       bytes: attachment.data,
 | 
			
		||||
//       ext: attachment.name.toString().substring(attachment.name.toString().lastIndexOf('.')+1)
 | 
			
		||||
//     );
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
| 
						 | 
				
			
			@ -1,103 +1,3 @@
 | 
			
		|||
import "dart:typed_data";
 | 
			
		||||
 | 
			
		||||
import "package:crab_ui/attachmentDownload.dart";
 | 
			
		||||
import "package:crab_ui/structs.dart";
 | 
			
		||||
import "package:flutter/material.dart";
 | 
			
		||||
import 'package:pdfrx/pdfrx.dart';
 | 
			
		||||
import 'package:photo_view/photo_view.dart';
 | 
			
		||||
 | 
			
		||||
class AttachmentWidget extends StatelessWidget {
 | 
			
		||||
  final AttachmentResponse attachment;
 | 
			
		||||
  AttachmentWidget({required this.attachment});
 | 
			
		||||
 | 
			
		||||
  Widget attachmentViewer(AttachmentResponse att) {
 | 
			
		||||
    String extension = att.name
 | 
			
		||||
        .toString()
 | 
			
		||||
        .substring(att.name.toString().indexOf(".") + 1)
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
    if (extension == "jpg" || extension == "png") {
 | 
			
		||||
      return Image.memory(att.data);
 | 
			
		||||
    } else if (extension == "pdf") {
 | 
			
		||||
      return PdfViewer.data(Uint8List.fromList(att.data),
 | 
			
		||||
          sourceName: att.name,
 | 
			
		||||
          params: PdfViewerParams(
 | 
			
		||||
            enableTextSelection: true,
 | 
			
		||||
            scrollByMouseWheel: 0.5,
 | 
			
		||||
            annotationRenderingMode:
 | 
			
		||||
                PdfAnnotationRenderingMode.annotationAndForms,
 | 
			
		||||
          ));
 | 
			
		||||
    }
 | 
			
		||||
    return Center(
 | 
			
		||||
        child: Container(
 | 
			
		||||
          padding: EdgeInsets.all(20),
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: Color(0xff6C63FF),
 | 
			
		||||
            borderRadius: BorderRadius.circular(16),
 | 
			
		||||
            boxShadow: [
 | 
			
		||||
              BoxShadow(
 | 
			
		||||
                color: Colors.black26,
 | 
			
		||||
                blurRadius: 10,
 | 
			
		||||
              ),
 | 
			
		||||
            ],       
 | 
			
		||||
          ),
 | 
			
		||||
          child: Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: [
 | 
			
		||||
              Text(
 | 
			
		||||
                "No preview available",
 | 
			
		||||
                style: TextStyle(
 | 
			
		||||
                  color: Colors.white, fontSize: 18, decoration: TextDecoration.none),
 | 
			
		||||
              ),
 | 
			
		||||
              SizedBox(
 | 
			
		||||
                height: 5,
 | 
			
		||||
              ),
 | 
			
		||||
              GestureDetector(
 | 
			
		||||
                child: ElevatedButton(
 | 
			
		||||
                  child: Row(
 | 
			
		||||
                    mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Text("Download", style: TextStyle(color: Color(0xff2c3e50)),),
 | 
			
		||||
                      Icon(Icons.download,
 | 
			
		||||
                      color: Color(0xffb0b0b0),),
 | 
			
		||||
                  ]),
 | 
			
		||||
                  onPressed: () => Attachmentdownload().saveFile(att),
 | 
			
		||||
              )),
 | 
			
		||||
              ]),
 | 
			
		||||
        ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
        color: Colors.black38,
 | 
			
		||||
        child: Stack(children: <Widget>[
 | 
			
		||||
          Container(
 | 
			
		||||
            color: Colors.white,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: EdgeInsets.fromLTRB(10, 20, 0, 10),
 | 
			
		||||
              child: Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Row(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      CloseButton(onPressed: () => {Navigator.pop(context)}),
 | 
			
		||||
                      Text(
 | 
			
		||||
                        attachment.name
 | 
			
		||||
                            .toString(), //its alr a string but incase ¯\(ツ)/¯ //update: i did that everywhere lol
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                            color: Colors.black,
 | 
			
		||||
                            fontSize: 20,
 | 
			
		||||
                            decoration: TextDecoration
 | 
			
		||||
                                .none), //TODO: personalize your fonts
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: attachmentViewer(attachment),
 | 
			
		||||
                  )
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ]));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export 'attachmentWidgetStub.dart'
 | 
			
		||||
  if (dart.library.js_interop) 'attachmentWidgetWeb.dart'
 | 
			
		||||
  if (dart.library.io) 'attachmentWidgetAndroid.dart';
 | 
			
		||||
							
								
								
									
										16
									
								
								lib/attachmentWidgetAndroid.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import "package:crab_ui/structs.dart";
 | 
			
		||||
import "package:flutter/material.dart";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AttachmentWidget extends StatelessWidget {
 | 
			
		||||
  final AttachmentResponse attachment;
 | 
			
		||||
  AttachmentWidget({required this.attachment});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: Text("PDF EVENTUALLY ANDROID")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								lib/attachmentWidgetStub.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
 | 
			
		||||
class AttachmentWidget extends StatelessWidget{
 | 
			
		||||
  final AttachmentResponse attachment;
 | 
			
		||||
  AttachmentWidget({required this.attachment});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: Text("PDF EVENTUALLY, STUB")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								lib/attachmentWidgetWeb.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,101 @@
 | 
			
		|||
import "dart:typed_data";
 | 
			
		||||
import "package:crab_ui/attachmentDownload.dart";
 | 
			
		||||
import "package:crab_ui/structs.dart";
 | 
			
		||||
import "package:flutter/material.dart";
 | 
			
		||||
import 'package:pdfrx/pdfrx.dart';
 | 
			
		||||
 | 
			
		||||
class AttachmentWidget extends StatelessWidget {
 | 
			
		||||
  final AttachmentResponse attachment;
 | 
			
		||||
  AttachmentWidget({required this.attachment});
 | 
			
		||||
 | 
			
		||||
  Widget attachmentViewer(AttachmentResponse att) {
 | 
			
		||||
    String extension = att.name
 | 
			
		||||
        .toString()
 | 
			
		||||
        .substring(att.name.toString().indexOf(".") + 1)
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
    if (extension == "jpg" || extension == "png") {
 | 
			
		||||
      return Image.memory(att.data);
 | 
			
		||||
    } else if (extension == "pdf") {
 | 
			
		||||
      return PdfViewer.data(Uint8List.fromList(att.data),
 | 
			
		||||
          sourceName: att.name,
 | 
			
		||||
          params: PdfViewerParams(
 | 
			
		||||
            enableTextSelection: true,
 | 
			
		||||
            scrollByMouseWheel: 0.5,
 | 
			
		||||
            annotationRenderingMode:
 | 
			
		||||
              PdfAnnotationRenderingMode.annotationAndForms,
 | 
			
		||||
          ));
 | 
			
		||||
    }
 | 
			
		||||
    return Center(
 | 
			
		||||
        child: Container(
 | 
			
		||||
          padding: EdgeInsets.all(20),
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: Color(0xff6C63FF),
 | 
			
		||||
            borderRadius: BorderRadius.circular(16),
 | 
			
		||||
            boxShadow: [
 | 
			
		||||
              BoxShadow(
 | 
			
		||||
                color: Colors.black26,
 | 
			
		||||
                blurRadius: 10,
 | 
			
		||||
              ),
 | 
			
		||||
            ],       
 | 
			
		||||
          ),
 | 
			
		||||
          child: Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: [
 | 
			
		||||
              Text(
 | 
			
		||||
                "No preview available",
 | 
			
		||||
                style: TextStyle(
 | 
			
		||||
                  color: Colors.white, fontSize: 18, decoration: TextDecoration.none),
 | 
			
		||||
              ),
 | 
			
		||||
              SizedBox(
 | 
			
		||||
                height: 5,
 | 
			
		||||
              ),
 | 
			
		||||
              GestureDetector(
 | 
			
		||||
                child: ElevatedButton(
 | 
			
		||||
                  child: Row(
 | 
			
		||||
                    mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Text("Download", style: TextStyle(color: Color(0xff2c3e50)),),
 | 
			
		||||
                      Icon(Icons.download,
 | 
			
		||||
                      color: Color(0xffb0b0b0),),
 | 
			
		||||
                  ]),
 | 
			
		||||
                  onPressed: () => Attachmentdownload().saveFile(att),
 | 
			
		||||
              )),
 | 
			
		||||
              ]),
 | 
			
		||||
        ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
        color: Colors.black38,
 | 
			
		||||
        child: Stack(children: <Widget>[
 | 
			
		||||
          Container(
 | 
			
		||||
            color: Colors.white,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: EdgeInsets.fromLTRB(10, 20, 0, 10),
 | 
			
		||||
              child: Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Row(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      CloseButton(onPressed: () => {Navigator.pop(context)}),
 | 
			
		||||
                      Text(
 | 
			
		||||
                        attachment.name
 | 
			
		||||
                            .toString(), //its alr a string but incase ¯\(ツ)/¯ //update: i did that everywhere lol
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                            color: Colors.black,
 | 
			
		||||
                            fontSize: 20,
 | 
			
		||||
                            decoration: TextDecoration
 | 
			
		||||
                                .none), //TODO: personalize your fonts
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: attachmentViewer(attachment),
 | 
			
		||||
                  )
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ]));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										477
									
								
								lib/augment.dart
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,24 +1,33 @@
 | 
			
		|||
// import 'dart:ffi';
 | 
			
		||||
 | 
			
		||||
import 'package:crab_ui/api_service.dart';
 | 
			
		||||
import 'package:crab_ui/attachmentDownload.dart';
 | 
			
		||||
import 'package:crab_ui/collapsableEmails.dart';
 | 
			
		||||
import 'package:crab_ui/structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:pdfrx/pdfrx.dart';
 | 
			
		||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
 | 
			
		||||
// import 'dart:html' as html;
 | 
			
		||||
// import 'dart:js' as js;
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
 | 
			
		||||
import 'attachmentWidget.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'routingHandler.dart';
 | 
			
		||||
 | 
			
		||||
class EmailToolbar extends StatefulWidget {
 | 
			
		||||
  final Function(String) onJumpToSpan;
 | 
			
		||||
  final Function(String) onJumpToNumbering;
 | 
			
		||||
  final Function(String) onViewspecs;
 | 
			
		||||
  final VoidCallback onButtonPressed;
 | 
			
		||||
  final Function(String) onFiltering;
 | 
			
		||||
  final List<String> emails;
 | 
			
		||||
  final String subject;
 | 
			
		||||
  late AugmentTree? rootAugment;
 | 
			
		||||
 | 
			
		||||
  EmailToolbar(
 | 
			
		||||
      {Key? key, required this.onButtonPressed, required this.onJumpToSpan})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  EmailToolbar({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.onButtonPressed,
 | 
			
		||||
    required this.onJumpToNumbering,
 | 
			
		||||
    required this.onViewspecs,
 | 
			
		||||
    required this.onFiltering,
 | 
			
		||||
    required this.emails,
 | 
			
		||||
    required this.subject,
 | 
			
		||||
    required this.rootAugment,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _DynamicClassesAugment createState() => _DynamicClassesAugment();
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +35,10 @@ class EmailToolbar extends StatefulWidget {
 | 
			
		|||
 | 
			
		||||
class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		||||
  String selectedClass = 'Class 1';
 | 
			
		||||
  // TextEditingController _jumpController = TextEditingController();
 | 
			
		||||
  TextEditingController _jumpController = TextEditingController();
 | 
			
		||||
  TextEditingController _viewspecsController = TextEditingController();
 | 
			
		||||
  AugmentTree? localAugment;
 | 
			
		||||
  List<SerializableMessage>? emailsInThread;
 | 
			
		||||
 | 
			
		||||
  // late final FocusNode _JumpItemfocusNode;
 | 
			
		||||
  // late final FocusNode _viewSpecsfocusNode;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,13 +59,25 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
    // _viewSpecsfocusNode.addListener(() {
 | 
			
		||||
    //   setState(() => _viewSpecsHasFocus = _viewSpecsfocusNode.hasFocus);
 | 
			
		||||
    // });
 | 
			
		||||
    localAugment = widget.rootAugment;
 | 
			
		||||
    _serializableData(widget.emails);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _serializableData(List<String> threadID) async {
 | 
			
		||||
    // emailsInThread = await ApiService().threadsInSerializable();
 | 
			
		||||
    print("done thread serializable");
 | 
			
		||||
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    // setState(() {
 | 
			
		||||
    //   _isLoaded = true;
 | 
			
		||||
    // });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    // _JumpItemfocusNode.dispose();
 | 
			
		||||
    // _viewSpecsfocusNode.dispose();
 | 
			
		||||
    // _jumpController.dispose();
 | 
			
		||||
    _jumpController.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -78,20 +102,20 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
            child: Text('Attachments'),
 | 
			
		||||
          ),
 | 
			
		||||
          SizedBox(width: 8),
 | 
			
		||||
          ElevatedButton(
 | 
			
		||||
            onPressed: AugmentClasses.handleOpen,
 | 
			
		||||
            child: Text('Open'),
 | 
			
		||||
          ),
 | 
			
		||||
          // ElevatedButton(
 | 
			
		||||
          //   onPressed: AugmentClasses.handleOpen,
 | 
			
		||||
          //   child: Text('Open'),
 | 
			
		||||
          // ),
 | 
			
		||||
          // SizedBox(width: 8),
 | 
			
		||||
          ElevatedButton(
 | 
			
		||||
            onPressed: AugmentClasses.handleFind,
 | 
			
		||||
            child: Text('Find'),
 | 
			
		||||
          ),
 | 
			
		||||
          // SizedBox(width: 8),
 | 
			
		||||
          ElevatedButton(
 | 
			
		||||
            onPressed: AugmentClasses.handleStop,
 | 
			
		||||
            child: Text('Stop'),
 | 
			
		||||
          ),
 | 
			
		||||
          // ElevatedButton(
 | 
			
		||||
          //   onPressed: AugmentClasses.handleStop,
 | 
			
		||||
          //   child: Text('Stop'),
 | 
			
		||||
          // ),
 | 
			
		||||
          ElevatedButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              AugmentClasses.handleMove(context);
 | 
			
		||||
| 
						 | 
				
			
			@ -138,10 +162,10 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
              //   width: 8,
 | 
			
		||||
              // ),
 | 
			
		||||
              Container(
 | 
			
		||||
                width: 50,
 | 
			
		||||
                width: 100,
 | 
			
		||||
                height: 30,
 | 
			
		||||
                child: TextField(
 | 
			
		||||
                  // controller: _jumpController,
 | 
			
		||||
                  controller: _jumpController,
 | 
			
		||||
                  decoration: InputDecoration(
 | 
			
		||||
                    border: OutlineInputBorder(),
 | 
			
		||||
                    // suffixIcon: Icon(Icons.search)
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +173,7 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
                  onSubmitted: (value) {
 | 
			
		||||
                    print("onSubmitted");
 | 
			
		||||
                    if (value.isNotEmpty) {
 | 
			
		||||
                      widget.onJumpToSpan(value);
 | 
			
		||||
                      widget.onJumpToNumbering(value);
 | 
			
		||||
                    }
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
| 
						 | 
				
			
			@ -186,18 +210,23 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
                  onPressed: () => AugmentClasses.ViewSpecsButton(context),
 | 
			
		||||
                  child: Text('ViewSpecs:')),
 | 
			
		||||
              Container(
 | 
			
		||||
                width: 50,
 | 
			
		||||
                width: 100,
 | 
			
		||||
                height: 30,
 | 
			
		||||
                child: TextField(
 | 
			
		||||
                  controller: _viewspecsController,
 | 
			
		||||
                  decoration: InputDecoration(
 | 
			
		||||
                    labelText: '',
 | 
			
		||||
                    border: OutlineInputBorder(),
 | 
			
		||||
                    // suffixIcon: Icon(Icons.style_rounded)
 | 
			
		||||
                  ),
 | 
			
		||||
                  onSubmitted: (value) {
 | 
			
		||||
                    widget.onViewspecs(value);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                onPressed: () => AugmentClasses.FilterButton(context),
 | 
			
		||||
                onPressed: () =>
 | 
			
		||||
                    AugmentClasses().filterButton(context, widget.onFiltering),
 | 
			
		||||
                child: Text('Filter'),
 | 
			
		||||
              ),
 | 
			
		||||
              SizedBox(width: 8),
 | 
			
		||||
| 
						 | 
				
			
			@ -207,11 +236,14 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
              ),
 | 
			
		||||
              // SizedBox(width: 8),
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                onPressed: AugmentClasses.handleFind,
 | 
			
		||||
                onPressed: () => AugmentClasses()
 | 
			
		||||
                    .handleCreateLink(context, widget.emails, widget.subject, widget.emails[0]), //need to add the email ids
 | 
			
		||||
                child: Text('Create Link'),
 | 
			
		||||
              ),
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                onPressed: AugmentClasses.handleFind,
 | 
			
		||||
                // onPressed: () => localAugment!.handlePaste(context),
 | 
			
		||||
                onPressed: () =>
 | 
			
		||||
                    AugmentClasses().handlePaste(context),
 | 
			
		||||
                child: Text('Paste Link'),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
| 
						 | 
				
			
			@ -222,6 +254,18 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
class AugmentClasses {
 | 
			
		||||
  CollapsableEmails? localCollapsable;
 | 
			
		||||
  String? nameOfDocument;
 | 
			
		||||
  AugmentTree? rootTree;
 | 
			
		||||
 | 
			
		||||
  void setRootTree(AugmentTree aTree) {
 | 
			
		||||
    rootTree = aTree;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // AugmentClasses(CollapsableEmails localCollapsable) {
 | 
			
		||||
  //   localCollapsable = localCollapsable;
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  static OverlayEntry? _overlayEntry;
 | 
			
		||||
  static String? selectedFolder; // Manage selected folder at the class level
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -487,45 +531,236 @@ class AugmentClasses {
 | 
			
		|||
    print("Find button pressed");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AugmentTree? _findAugmentNode(String target, AugmentTree node, int index) {
 | 
			
		||||
    // so the ideqa is that since the numbering its quite linear, meaning that it tells you where it goes,
 | 
			
		||||
    // thus i've thought the amount of max moves are only the length of the string of the target
 | 
			
		||||
    //e.g. if we have a target of 1e9, its steps are the same or time complexity as if it were 1a1, or 99z99
 | 
			
		||||
    // since each number or letter tells us which is the index in this array, genius ik
 | 
			
		||||
    // thus first one needs another function from converting from alphabetical to numbers
 | 
			
		||||
    if (node.numbering[index] == target[index]) {
 | 
			
		||||
      _findAugmentNode(target, node.children[index++], index++);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool _checkValidTarget(String target) {
 | 
			
		||||
    target = target.trim();
 | 
			
		||||
    //find if the target exists,
 | 
			
		||||
    //recursive?
 | 
			
		||||
    _findAugmentNode(target, rootTree!, 0);
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _copyLink(String anchor, String target, String format, String viewspecs,
 | 
			
		||||
      String nameOfDocument, emailID) {
 | 
			
		||||
    String form = "$anchor < $nameOfDocument, $target :$viewspecs > $emailID";
 | 
			
		||||
    final link = ClipboardData(text: form);
 | 
			
		||||
    Clipboard.setData(link);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> handleCreateLink(BuildContext context,
 | 
			
		||||
      List<String> emailsInThread, String nameOfDocument, String emailID) async {
 | 
			
		||||
    print("create link button pressed");
 | 
			
		||||
    final TextEditingController targetController = TextEditingController();
 | 
			
		||||
    final TextEditingController anchorController = TextEditingController();
 | 
			
		||||
    final TextEditingController viewspecsController = TextEditingController();
 | 
			
		||||
    final TextEditingController formatController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
    // String anchorPhrase = '';
 | 
			
		||||
    String format = 'augment';
 | 
			
		||||
    // String target = '';
 | 
			
		||||
    // String viewspecs = '';
 | 
			
		||||
 | 
			
		||||
    await showDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        builder: (context) => AlertDialog(
 | 
			
		||||
              title: Text('Create URL Link'),
 | 
			
		||||
              content: SizedBox(
 | 
			
		||||
                height: 400,
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text("Which email? "),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 350.0,
 | 
			
		||||
                          child: Text(nameOfDocument),
 | 
			
		||||
                          // child: ListView.builder(
 | 
			
		||||
                          //     itemCount: emailsInThread.length,
 | 
			
		||||
                          //     itemBuilder: (context, index) {
 | 
			
		||||
                          //       // var item = emailsInThread[index];
 | 
			
		||||
                          //       // ApiService().
 | 
			
		||||
                          //       return ListTile(
 | 
			
		||||
                          //         title: Text(nameOfDocument),
 | 
			
		||||
                          //       );
 | 
			
		||||
                          //     }),
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text("Link to target item at: "),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 350.0,
 | 
			
		||||
                          child: TextField(
 | 
			
		||||
                            controller: targetController,
 | 
			
		||||
                            autofocus: true,
 | 
			
		||||
                            maxLines: 1,
 | 
			
		||||
                            decoration: const InputDecoration(
 | 
			
		||||
                              border: OutlineInputBorder(),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    SizedBox(height: 8),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        ElevatedButton(
 | 
			
		||||
                            onPressed: () => ViewSpecsButton(context),
 | 
			
		||||
                            child: Text("Viewspecs:")),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 150.0,
 | 
			
		||||
                          child: TextField(
 | 
			
		||||
                            controller: viewspecsController,
 | 
			
		||||
                            maxLines: 1,
 | 
			
		||||
                            decoration: const InputDecoration(
 | 
			
		||||
                              border: OutlineInputBorder(),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    SizedBox(height: 8),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text("Using anchor phrase: "),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 150.0,
 | 
			
		||||
                          child: TextField(
 | 
			
		||||
                            controller: anchorController,
 | 
			
		||||
                            maxLines: 1,
 | 
			
		||||
                            decoration: const InputDecoration(
 | 
			
		||||
                              border: OutlineInputBorder(),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    SizedBox(height: 8),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text("Using link format: "),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 250.0,
 | 
			
		||||
                          child: Text("Augment"),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              actions: [
 | 
			
		||||
                ElevatedButton(
 | 
			
		||||
                    onPressed: () => {
 | 
			
		||||
                          // _checkValidTarget(targetController.text),
 | 
			
		||||
 | 
			
		||||
                          _copyLink(
 | 
			
		||||
                              anchorController.text,
 | 
			
		||||
                              targetController.text,
 | 
			
		||||
                              format,
 | 
			
		||||
                              viewspecsController.text,
 | 
			
		||||
                              nameOfDocument,
 | 
			
		||||
                              emailID,
 | 
			
		||||
                              ),
 | 
			
		||||
                          Navigator.of(context).pop()
 | 
			
		||||
                        },
 | 
			
		||||
                    child: Text("OK")),
 | 
			
		||||
                ElevatedButton(
 | 
			
		||||
                    onPressed: () => Navigator.of(context).pop(),
 | 
			
		||||
                    child: Text("Cancel")),
 | 
			
		||||
                ElevatedButton(onPressed: null, child: Text("Help")),
 | 
			
		||||
              ],
 | 
			
		||||
            ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> handlePaste(BuildContext context) async {
 | 
			
		||||
    final TextEditingController gotoLink = TextEditingController();
 | 
			
		||||
 | 
			
		||||
    Routinghandler localRouting;
 | 
			
		||||
    await showDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        builder: (context) => AlertDialog(
 | 
			
		||||
              title: Text("GOTO Link"),
 | 
			
		||||
              content: SizedBox(
 | 
			
		||||
                height: 400,
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text("Paste link to go: "),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                            width: 350,
 | 
			
		||||
                            child: TextField(
 | 
			
		||||
                              controller: gotoLink,
 | 
			
		||||
                              maxLines: 1,
 | 
			
		||||
                              autofocus: true,
 | 
			
		||||
                              decoration: const InputDecoration(
 | 
			
		||||
                                border: OutlineInputBorder(),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ))
 | 
			
		||||
                      ],
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              actions: [
 | 
			
		||||
                ElevatedButton(
 | 
			
		||||
                    onPressed: () {
 | 
			
		||||
                      print('pressed');
 | 
			
		||||
                      Navigator.of(context).pop();
 | 
			
		||||
                      final localRouting =
 | 
			
		||||
                          Routinghandler(gotoLink.text);
 | 
			
		||||
                      final String subject =
 | 
			
		||||
                          localRouting.docName; // This is your :subject
 | 
			
		||||
                      final String target =
 | 
			
		||||
                          localRouting.target; // This is your :target
 | 
			
		||||
                      final String viewspecs =
 | 
			
		||||
                          localRouting.viewspecs; // This is your :viewspecs
 | 
			
		||||
                      final String finalEmailID = localRouting.emailID;
 | 
			
		||||
 | 
			
		||||
                      final encodedSubject = Uri.encodeComponent(subject);
 | 
			
		||||
                      final encodedTarget = Uri.encodeComponent(target);
 | 
			
		||||
                      final encodedViewspecs = Uri.encodeComponent(viewspecs);
 | 
			
		||||
                      final encodedEmailID = Uri.encodeComponent(finalEmailID);
 | 
			
		||||
                      print("emailID $encodedEmailID");
 | 
			
		||||
                      String link =
 | 
			
		||||
                          "/email/$encodedSubject/$encodedTarget/$encodedViewspecs/$encodedEmailID";
 | 
			
		||||
                      GoRouter.of(context).go(link);
 | 
			
		||||
                    },
 | 
			
		||||
                    child: Text("OK")),
 | 
			
		||||
                ElevatedButton(
 | 
			
		||||
                    onPressed: () {
 | 
			
		||||
                      Navigator.of(context).pop();
 | 
			
		||||
                    },
 | 
			
		||||
                    child: Text("Cancel"))
 | 
			
		||||
              ],
 | 
			
		||||
            ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void handleStop() {
 | 
			
		||||
    print("Stop button pressed");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void handleJump(String spanId) {
 | 
			
		||||
    String js_code = '''
 | 
			
		||||
      var iframe = document.getElementsByTagName('iframe')[0]; // 0 for the first iframe, 1 for the second, etc.
 | 
			
		||||
 | 
			
		||||
      // Check if the iframe is loaded and has content
 | 
			
		||||
      if (iframe && iframe.contentDocument) {
 | 
			
		||||
          // Access the document inside the iframe
 | 
			
		||||
          var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
 | 
			
		||||
 | 
			
		||||
          // Find the element with the specific id inside the iframe
 | 
			
		||||
          var targetElement = iframeDoc.getElementById("$spanId"); // Replace '36 ' with the actual id of the target element
 | 
			
		||||
 | 
			
		||||
          // If the element exists, scroll to it
 | 
			
		||||
          if (targetElement) {
 | 
			
		||||
              targetElement.scrollIntoView();
 | 
			
		||||
              console.log('Scrolled to element with id "$spanId" inside the iframe.');
 | 
			
		||||
          } else {
 | 
			
		||||
              console.log('Element with id "$spanId" not found inside the iframe.');
 | 
			
		||||
          }
 | 
			
		||||
      } else {
 | 
			
		||||
          console.log('Iframe not found or not loaded.');
 | 
			
		||||
      }
 | 
			
		||||
      ''';
 | 
			
		||||
    // js.context.callMethod('eval', [js_code]);
 | 
			
		||||
  static void handleJump(String value) {
 | 
			
		||||
    print(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void invisibility(String htmlClass) {}
 | 
			
		||||
 | 
			
		||||
  static Future<void> JumpButton(BuildContext context) async {
 | 
			
		||||
    // FocusNode textFieldFocusNode = FocusNode();
 | 
			
		||||
 | 
			
		||||
    // AugmentClasses.disableIframePointerEvents();
 | 
			
		||||
    await showDialog(
 | 
			
		||||
      barrierDismissible: true,
 | 
			
		||||
      // barrierColor: Colors.yellow,
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (context) => AlertDialog(
 | 
			
		||||
        title: Text('Jump Item:'),
 | 
			
		||||
| 
						 | 
				
			
			@ -725,7 +960,6 @@ class AugmentClasses {
 | 
			
		|||
                          ElevatedButton(onPressed: () {}, child: Text('OK')),
 | 
			
		||||
                          ElevatedButton(
 | 
			
		||||
                              onPressed: () {
 | 
			
		||||
                                // AugmentClasses.disableIframePointerEvents();
 | 
			
		||||
                                Navigator.of(context).pop();
 | 
			
		||||
                              },
 | 
			
		||||
                              child: Text('Cancel')),
 | 
			
		||||
| 
						 | 
				
			
			@ -743,61 +977,90 @@ class AugmentClasses {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void handleFilter() {}
 | 
			
		||||
  static Future<void> FilterButton(context) async {
 | 
			
		||||
  Future<List<AugmentTree>> searchFilter(String query) async {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> filterButton(
 | 
			
		||||
      context, Function(String) onFilteringCallback) async {
 | 
			
		||||
    //this is literally ctrl+F :skull:
 | 
			
		||||
    //idea is to search in file, extract the <p> tags that contain these
 | 
			
		||||
    //words and highlight, then when zoom, you just jump to that paragraph
 | 
			
		||||
    bool? numbering = false;
 | 
			
		||||
    String filterQueue = '';
 | 
			
		||||
 | 
			
		||||
    // AugmentClasses.disableIframePointerEvents();
 | 
			
		||||
    await showDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        builder: (context) => Container(
 | 
			
		||||
            height: 150,
 | 
			
		||||
            width: 300,
 | 
			
		||||
            child: AlertDialog(
 | 
			
		||||
                title: Text('Filter'),
 | 
			
		||||
                content: Container(
 | 
			
		||||
                    width: 400, // Set the width to simulate the Windows style
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text('Set filter:'),
 | 
			
		||||
                        SizedBox(
 | 
			
		||||
                          width: 175,
 | 
			
		||||
                          child: TextField(
 | 
			
		||||
                            maxLines: 1,
 | 
			
		||||
                            decoration: InputDecoration(
 | 
			
		||||
                              border: OutlineInputBorder(),
 | 
			
		||||
                            ),
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (BuildContext dialogContext) {
 | 
			
		||||
        return StatefulBuilder(builder:
 | 
			
		||||
            (BuildContext statefulBuilderContext, StateSetter setState) {
 | 
			
		||||
          return AlertDialog(
 | 
			
		||||
            title: const Text('Filter'),
 | 
			
		||||
            content: SizedBox(
 | 
			
		||||
                width: 400, // Set the width to simulate the Windows style
 | 
			
		||||
                child: Column(
 | 
			
		||||
                    mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Text('Set filter:'),
 | 
			
		||||
                      SizedBox(
 | 
			
		||||
                        width: 175,
 | 
			
		||||
                        child: TextField(
 | 
			
		||||
                          autofocus: true,
 | 
			
		||||
                          maxLines: 1,
 | 
			
		||||
                          decoration: const InputDecoration(
 | 
			
		||||
                            border: OutlineInputBorder(),
 | 
			
		||||
                          ),
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    )))));
 | 
			
		||||
                          onChanged: (value) {
 | 
			
		||||
                            print(value);
 | 
			
		||||
                            filterQueue = value;
 | 
			
		||||
                          },
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                      SizedBox(
 | 
			
		||||
                        height: 10,
 | 
			
		||||
                      ),
 | 
			
		||||
                      Column(children: [
 | 
			
		||||
                        Row(children: [
 | 
			
		||||
                          Checkbox(
 | 
			
		||||
                              value: numbering,
 | 
			
		||||
                              activeColor:
 | 
			
		||||
                                  Theme.of(context).colorScheme.tertiary,
 | 
			
		||||
                              onChanged: (newBool) {
 | 
			
		||||
                                setState(() {
 | 
			
		||||
                                  numbering = newBool;
 | 
			
		||||
                                });
 | 
			
		||||
                              }),
 | 
			
		||||
                          Text("Start at top of file")
 | 
			
		||||
                        ]),
 | 
			
		||||
                      ]),
 | 
			
		||||
                    ])),
 | 
			
		||||
            actions: [
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    Navigator.of(context).pop();
 | 
			
		||||
                  },
 | 
			
		||||
                  child: Text("Cancel")),
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    Navigator.of(context).pop({
 | 
			
		||||
                      'filterQueue': filterQueue,
 | 
			
		||||
                      'numbering': numbering,
 | 
			
		||||
                    });
 | 
			
		||||
                  },
 | 
			
		||||
                  child: Text("Apply")),
 | 
			
		||||
            ],
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
    ).then((result) {
 | 
			
		||||
      if (result != null) {
 | 
			
		||||
        print("filter done $result");
 | 
			
		||||
        final String query = result['filterQueue'];
 | 
			
		||||
        onFilteringCallback(query);
 | 
			
		||||
      } else {
 | 
			
		||||
        print('cancelled');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // static void disableIframePointerEvents() {
 | 
			
		||||
  //   //pretty sure these dont work
 | 
			
		||||
  //   // final iframes = html.document.getElementsByTagName('iframe');
 | 
			
		||||
  //   final iframes = web.document.getElementsByTagName('iframe');
 | 
			
		||||
  //   for (var iframe in iframes) {
 | 
			
		||||
  //     // if (iframe is html.Element) {
 | 
			
		||||
  //     //   iframe.style.pointerEvents = 'none'; // Disable pointer events
 | 
			
		||||
  //     if (iframe is web.Element) {
 | 
			
		||||
  //       iframe.
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // static void enableIframePointerEvents() {
 | 
			
		||||
  //   // final iframes = html.document.getElementsByTagName('iframe');
 | 
			
		||||
  //   final iframes = html.document.getElementsByTagName('iframe');
 | 
			
		||||
 | 
			
		||||
  //   for (var iframe in iframes) {
 | 
			
		||||
  //     if (iframe is html.Element) {
 | 
			
		||||
  //       iframe.style.pointerEvents = 'auto'; // Re-enable pointer events
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,132 +1,3 @@
 | 
			
		|||
import 'dart:js_interop';
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'dart:ui_web' as ui;
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
 | 
			
		||||
class CollapsableEmails extends StatefulWidget {
 | 
			
		||||
  final List<String> thread; // email id's in the form xyz@gmail.com
 | 
			
		||||
  final List<String> threadHTML;
 | 
			
		||||
  final String threadIDs;
 | 
			
		||||
 | 
			
		||||
  CollapsableEmails(
 | 
			
		||||
      {required this.thread,
 | 
			
		||||
      required this.threadHTML,
 | 
			
		||||
      required this.threadIDs});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CollapsableEmails> createState() => _CollapsableEmailsState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		||||
  List<String> emailsHTML = []; //html of the emails in the thread
 | 
			
		||||
  // build attachments with the forldar name and id
 | 
			
		||||
  Set<int> _expandedEmails = {}; //open emails
 | 
			
		||||
  List viewtypeIDs = []; //IDs of the viewtypes, order matters
 | 
			
		||||
  List heightOfViewTypes = []; //the height of each viewtype
 | 
			
		||||
  List<SerializableMessage> emailsInThread = [];
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    // TODO: implement initState
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _registerViewFactory(widget.threadHTML);
 | 
			
		||||
    _serializableData(widget.threadIDs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _registerViewFactory(List<String> currentContent) async {
 | 
			
		||||
    // setState(() { //update to do item per item
 | 
			
		||||
    // each item to have itsviewtype ID
 | 
			
		||||
    // is this necessarey here??
 | 
			
		||||
 | 
			
		||||
    //could just move to collapsable
 | 
			
		||||
 | 
			
		||||
    for (var emailHTML in widget.threadHTML) {
 | 
			
		||||
      String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
 | 
			
		||||
 | 
			
		||||
      final ghost = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
        ..style.visibility = 'hidden'
 | 
			
		||||
        ..style.position = 'absolute'
 | 
			
		||||
        ..style.width = '100%'
 | 
			
		||||
        ..style.overflow = 'auto'
 | 
			
		||||
        ..innerHTML = emailHTML
 | 
			
		||||
            .toJS; // temporarily index because it has to do all of them
 | 
			
		||||
      web.document.body?.append(ghost);
 | 
			
		||||
      await Future.delayed(Duration(milliseconds: 10));
 | 
			
		||||
 | 
			
		||||
      final heightOfEmail = ghost.scrollHeight;
 | 
			
		||||
      ghost.remove();
 | 
			
		||||
 | 
			
		||||
      final HTMLsnippet = web.document.createElement('div')
 | 
			
		||||
          as web.HTMLDivElement
 | 
			
		||||
        ..id = viewTypeId
 | 
			
		||||
        ..innerHTML = emailHTML
 | 
			
		||||
            .toJS; // temporarily index because it has to do all of them
 | 
			
		||||
      HTMLsnippet.style
 | 
			
		||||
        ..width = '100%'
 | 
			
		||||
        ..height = '${heightOfEmail}px'
 | 
			
		||||
        ..overflow = 'auto'
 | 
			
		||||
        ..scrollBehavior = 'smooth';
 | 
			
		||||
 | 
			
		||||
      ui.platformViewRegistry.registerViewFactory(
 | 
			
		||||
        viewTypeId,
 | 
			
		||||
        (int viewId) => HTMLsnippet,
 | 
			
		||||
      );
 | 
			
		||||
      viewtypeIDs.add(viewTypeId);
 | 
			
		||||
      heightOfViewTypes.add(heightOfEmail);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _serializableData(String threadID) async {
 | 
			
		||||
    emailsInThread = await ApiService().threadsInSerializable(threadID);
 | 
			
		||||
    print("done thread serializable");
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return _isLoaded 
 | 
			
		||||
      ?Column(children: [
 | 
			
		||||
        Expanded(
 | 
			
		||||
          child: ListView.builder(
 | 
			
		||||
            itemCount: widget.thread.length,
 | 
			
		||||
            itemBuilder: (context, index) {
 | 
			
		||||
              final isExpanded =
 | 
			
		||||
                  _expandedEmails.contains(index); //check if email is expanded
 | 
			
		||||
              return Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    title: Text(emailsInThread[index].from),
 | 
			
		||||
                    trailing: Text(emailsInThread[index].date),
 | 
			
		||||
                    onTap: () {
 | 
			
		||||
                      setState(() {
 | 
			
		||||
                        if (isExpanded) {
 | 
			
		||||
                          _expandedEmails.remove(index);
 | 
			
		||||
                        } else {
 | 
			
		||||
                          _expandedEmails.add(index);
 | 
			
		||||
                        }
 | 
			
		||||
                      });
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
                  if (isExpanded)
 | 
			
		||||
                    // if(viewtypeIDs[index] == null || heightOfViewTypes[index] == null)
 | 
			
		||||
                    //   const SizedBox(height: 100, child: Center(child: CircularProgressIndicator())),
 | 
			
		||||
                    SizedBox(
 | 
			
		||||
                      height: heightOfViewTypes[index].toDouble(),
 | 
			
		||||
                      child: HtmlElementView(
 | 
			
		||||
                          key: UniqueKey(), viewType: viewtypeIDs[index]),
 | 
			
		||||
                    ),
 | 
			
		||||
                  Divider(),
 | 
			
		||||
                ],
 | 
			
		||||
              );
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
    ]): const Center(child:CircularProgressIndicator());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export 'collapsableEmailsStub.dart'
 | 
			
		||||
  if (dart.library.io) 'collapsableEmailsAndroid.dart'
 | 
			
		||||
  if (dart.library.js_interop) 'collapsableEmailsWeb.dart';
 | 
			
		||||
							
								
								
									
										442
									
								
								lib/collapsableEmailsAndroid.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,442 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:html2md/html2md.dart' as html2md;
 | 
			
		||||
import 'package:markdown_widget/markdown_widget.dart';
 | 
			
		||||
import 'package:markdown/markdown.dart' as md;
 | 
			
		||||
 | 
			
		||||
class CollapsableEmails extends StatefulWidget {
 | 
			
		||||
  final List<String> thread; // email id's in the form xyz@gmail.com
 | 
			
		||||
  // final List<String> threadHTML;
 | 
			
		||||
  final List<String> threadMarkdown;
 | 
			
		||||
  final String threadIDs;
 | 
			
		||||
  final String? targetJumpNumbering;
 | 
			
		||||
  final String? targetViewspecs;
 | 
			
		||||
  final String? targetFiltering;
 | 
			
		||||
 | 
			
		||||
  CollapsableEmails({
 | 
			
		||||
    required this.thread,
 | 
			
		||||
    required this.threadMarkdown,
 | 
			
		||||
    required this.threadIDs,
 | 
			
		||||
    this.targetJumpNumbering,
 | 
			
		||||
    this.targetViewspecs,
 | 
			
		||||
    this.targetFiltering,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CollapsableEmails> createState() => _CollapsableEmailsState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		||||
  List<String> emailsHTML = []; //html of the emails in the thread
 | 
			
		||||
  // build attachments with the forldar name and id
 | 
			
		||||
  Set<int> _expandedEmails = {}; //open emails
 | 
			
		||||
  List<SerializableMessage> emailsInThread = [];
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
 | 
			
		||||
  List<String> hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"];
 | 
			
		||||
  Map<String, int> hirarchyDict = {
 | 
			
		||||
    "h1": 1,
 | 
			
		||||
    "h2": 2,
 | 
			
		||||
    "h3": 3,
 | 
			
		||||
    "h4": 4,
 | 
			
		||||
    "h5": 6,
 | 
			
		||||
    "h6": 7,
 | 
			
		||||
    "p": 8,
 | 
			
		||||
    "ul": 8,
 | 
			
		||||
    "li": 8,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  List<String> tagsCollected = [];
 | 
			
		||||
  List<String> allMarkdown = [];
 | 
			
		||||
  List<List<String>> sentinel = [];
 | 
			
		||||
  int level = 0;
 | 
			
		||||
  AugmentTree zoomTreeRoot = AugmentTree();
 | 
			
		||||
  // late AugmentTree currentZoomNode;
 | 
			
		||||
  late List<AugmentTree> currentZoomTree = [];
 | 
			
		||||
  bool zoomOut = false;
 | 
			
		||||
  bool zoomIn = true;
 | 
			
		||||
  late List<AugmentTree> threadNodes = [];
 | 
			
		||||
  static bool leftNumbering = false;
 | 
			
		||||
  static bool rightNumbering = true;
 | 
			
		||||
  bool showWhole = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    threadNodes = [];
 | 
			
		||||
    currentZoomTree = [];
 | 
			
		||||
    // _markdownConverter();
 | 
			
		||||
    _serializableData(widget.threadIDs); // this
 | 
			
		||||
    _markdown2Tree(widget.threadMarkdown);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void didUpdateWidget(covariant CollapsableEmails oldWidget) {
 | 
			
		||||
    // TODO: implement didUpdateWidget
 | 
			
		||||
    super.didUpdateWidget(oldWidget);
 | 
			
		||||
    if (widget.targetJumpNumbering != null &&
 | 
			
		||||
        widget.targetJumpNumbering != oldWidget.targetJumpNumbering) {
 | 
			
		||||
      _handleJump(widget.targetJumpNumbering!);
 | 
			
		||||
    }
 | 
			
		||||
    if (widget.targetViewspecs != null &&
 | 
			
		||||
        widget.targetViewspecs != oldWidget.targetViewspecs) {
 | 
			
		||||
      _handleViewspecs(widget.targetViewspecs!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  List<String> getThreads() {
 | 
			
		||||
    return widget.thread;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _add2Tree(AugmentTree tree, md.Element node2add) {
 | 
			
		||||
    // adds node to its corresponding place
 | 
			
		||||
    AugmentTree newNode = AugmentTree();
 | 
			
		||||
    newNode.setData(node2add.textContent);
 | 
			
		||||
    newNode.ogTag = node2add.tag;
 | 
			
		||||
    // cases,
 | 
			
		||||
    //1. a node that comes is lower than the root.children last, if so it goes beneath it
 | 
			
		||||
    if (tree.children.isEmpty) {
 | 
			
		||||
      // new level to be created when totally empty
 | 
			
		||||
      tree.children.add(newNode);
 | 
			
		||||
      newNode.parent = tree;
 | 
			
		||||
    } else if (tree.children.isNotEmpty &&
 | 
			
		||||
        tree.children.last.ogTag.isNotEmpty) {
 | 
			
		||||
      if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) < // e.g. new node is h1 and old is h2, heapify
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        //have to figure out the borthers
 | 
			
		||||
        //assuming it all goes right
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          return;
 | 
			
		||||
        } else if (tree.children.last.parent == null) {
 | 
			
		||||
          // becomes the new top level
 | 
			
		||||
          for (AugmentTree brother in tree.children) {
 | 
			
		||||
            brother.parent = newNode;
 | 
			
		||||
          }
 | 
			
		||||
          tree.children = [newNode];
 | 
			
		||||
        } else {
 | 
			
		||||
          newNode.parent = tree;
 | 
			
		||||
          tree.children.add(newNode);
 | 
			
		||||
        }
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) > // go down e.g. new node is h3 and old is h2 or something
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          print("-1 ${tree.children.last.ogTag}");
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _add2Tree(tree.children.last, node2add);
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ?? -1) ==
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        tree.children.add(newNode);
 | 
			
		||||
        newNode.parent = tree;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _markdown2Tree(List<String> text) {
 | 
			
		||||
    print("started markdown2tree");
 | 
			
		||||
    for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
 | 
			
		||||
      final List<md.Node> nakedList =
 | 
			
		||||
          md.Document().parseLines(text[emailsMD].split('\n'));
 | 
			
		||||
      zoomTreeRoot = AugmentTree();
 | 
			
		||||
      for (var node in nakedList) {
 | 
			
		||||
        //maybe do an add function, but isn't this it?
 | 
			
		||||
        if (node is md.Element) {
 | 
			
		||||
          AugmentTree temp = AugmentTree();
 | 
			
		||||
          temp.data = node.textContent;
 | 
			
		||||
          temp.ogTag = node.tag;
 | 
			
		||||
          if (node.tag == 'h1') {
 | 
			
		||||
            // make this O(1)
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node);
 | 
			
		||||
          } else if (node.tag == 'h2') {
 | 
			
		||||
            // i dont add any since i dont have it, maybe the function makes sense
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node); // fix this
 | 
			
		||||
          } else if (node.tag == 'h3') {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node);
 | 
			
		||||
          } else if (node.tag == 'h4') {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node); // change to temp
 | 
			
		||||
          } else if (node.tag == 'h5') {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node);
 | 
			
		||||
          } else if (node.tag == 'h6') {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node); // fix this
 | 
			
		||||
          } else if (node.tag == 'p' || node.tag == 'ul' || node.tag == 'li') {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node); // fix this
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      zoomTreeRoot.addNumbering();
 | 
			
		||||
      threadNodes.add(zoomTreeRoot);
 | 
			
		||||
      currentZoomTree.add(zoomTreeRoot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToChildren(int indexThread, int index) async {
 | 
			
		||||
    final target = currentZoomTree[indexThread].children[index];
 | 
			
		||||
    if (target.children.isNotEmpty) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = target;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("This child has no further children.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToParent(int indexThread) async {
 | 
			
		||||
    if (currentZoomTree[indexThread].parent != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("Already at root.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _serializableData(String threadID) async {
 | 
			
		||||
    emailsInThread = await ApiService().threadsInSerializable(threadID);
 | 
			
		||||
    print("done thread serializable");
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildForZooms(int indexThread) {
 | 
			
		||||
    // IF I GIVE IT THE INDEX????
 | 
			
		||||
    if (!_isLoaded) {
 | 
			
		||||
      return const Center(child: CircularProgressIndicator()); // loading screen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final AugmentTree currentZoomNodeForThisEmail =
 | 
			
		||||
        currentZoomTree[indexThread];
 | 
			
		||||
 | 
			
		||||
    final canZoomOut = currentZoomNodeForThisEmail.parent != null;
 | 
			
		||||
 | 
			
		||||
    return ListView.builder(
 | 
			
		||||
      itemCount: currentZoomNodeForThisEmail.children.length,
 | 
			
		||||
      itemBuilder: (context, index) {
 | 
			
		||||
        final childNode = currentZoomNodeForThisEmail.children[index];
 | 
			
		||||
        final canZoomIn = childNode.children.isNotEmpty;
 | 
			
		||||
        // currentZoomNodeForThisEmail.addNumbering();
 | 
			
		||||
        return Padding(
 | 
			
		||||
          padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
 | 
			
		||||
          child: Material(
 | 
			
		||||
            elevation: 1,
 | 
			
		||||
            borderRadius: BorderRadius.circular(12),
 | 
			
		||||
            color: Theme.of(context).colorScheme.surface,
 | 
			
		||||
            surfaceTintColor: Theme.of(context).colorScheme.surfaceBright,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(16.0),
 | 
			
		||||
              child: Row(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Wrap(
 | 
			
		||||
                    spacing: 4.0,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed:
 | 
			
		||||
                            canZoomOut ? () => _goToParent(indexThread) : null,
 | 
			
		||||
                        child: Icon(Icons.north_west_sharp),
 | 
			
		||||
                      ),
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed: canZoomIn
 | 
			
		||||
                            ? () => _goToChildren(indexThread, index)
 | 
			
		||||
                            : null,
 | 
			
		||||
                        child: Icon(Icons.south_east_sharp),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(width: 12.0),
 | 
			
		||||
                  if (leftNumbering)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        childNode.numbering,
 | 
			
		||||
                        style:
 | 
			
		||||
                            TextStyle(color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: MarkdownBlock(
 | 
			
		||||
                      data: childNode.data,
 | 
			
		||||
                      // data: currentZoomNode
 | 
			
		||||
                      //     .children[index].data, // one string of markdown
 | 
			
		||||
                      config: MarkdownConfig
 | 
			
		||||
                          .darkConfig, // or lightConfig depending on theme
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  if (rightNumbering)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.fromLTRB(5, 10, 5, 0),
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        childNode.numbering,
 | 
			
		||||
                        style:
 | 
			
		||||
                            TextStyle(color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleJump(String queryNumbering) {
 | 
			
		||||
    print(queryNumbering);
 | 
			
		||||
    if (queryNumbering.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter a numbering to jump to.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex];
 | 
			
		||||
    final AugmentTree? foundNode =
 | 
			
		||||
        _findNodeByNumbering(rootOfCurrentEmail, queryNumbering);
 | 
			
		||||
 | 
			
		||||
    if (foundNode != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[targetEmailIndex] = foundNode; // Update the state
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Numbering "$queryNumbering" not found.')),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleViewspecs(String viewspecsQuery) {
 | 
			
		||||
    print(viewspecsQuery);
 | 
			
		||||
    if (viewspecsQuery.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter the viewspecs.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (viewspecsQuery.contains('n')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = false; // Update the state
 | 
			
		||||
        rightNumbering = false;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('m')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = true;
 | 
			
		||||
        leftNumbering = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('H')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = !leftNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('G')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = !rightNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('w')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        showWhole = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // else {
 | 
			
		||||
    //   ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
    //     SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')),
 | 
			
		||||
    //   );
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) {
 | 
			
		||||
    //recursively finds the node you mentioned
 | 
			
		||||
    // to find the AugmentTree node corresponding to the `numbering`.
 | 
			
		||||
    if (root.numbering == numbering) {
 | 
			
		||||
      return root;
 | 
			
		||||
    }
 | 
			
		||||
    for (var child in root.children) {
 | 
			
		||||
      final found = _findNodeByNumbering(child, numbering);
 | 
			
		||||
      if (found != null) {
 | 
			
		||||
        return found;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return _isLoaded
 | 
			
		||||
        ? Column(children: [
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: ListView.builder(
 | 
			
		||||
                itemCount: emailsInThread.length,
 | 
			
		||||
                itemBuilder: (context, index) {
 | 
			
		||||
                  final isExpanded = _expandedEmails
 | 
			
		||||
                      .contains(index); //check if email is expanded
 | 
			
		||||
                  return Column(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      ListTile(
 | 
			
		||||
                        title: Text(emailsInThread[index].from),
 | 
			
		||||
                        trailing: Text(emailsInThread[index].date),
 | 
			
		||||
                        onTap: () {
 | 
			
		||||
                          setState(() {
 | 
			
		||||
                            if (isExpanded) {
 | 
			
		||||
                              _expandedEmails.remove(index);
 | 
			
		||||
                            } else {
 | 
			
		||||
                              _expandedEmails.add(index);
 | 
			
		||||
                            }
 | 
			
		||||
                          });
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                      if (isExpanded)
 | 
			
		||||
                        ConstrainedBox(
 | 
			
		||||
                          constraints: BoxConstraints(
 | 
			
		||||
                            minHeight: 100,
 | 
			
		||||
                            maxHeight: MediaQuery.of(context).size.height * 0.6,
 | 
			
		||||
                          ),
 | 
			
		||||
                          child: _buildForZooms(index),
 | 
			
		||||
                        ),
 | 
			
		||||
                      Divider(),
 | 
			
		||||
                    ],
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ])
 | 
			
		||||
        : const Center(child: CircularProgressIndicator());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								lib/collapsableEmailsStub.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class CollapsableEmails extends StatefulWidget {
 | 
			
		||||
  final List<String> thread; // email id's in the form xyz@gmail.com
 | 
			
		||||
  final List<String> threadMarkdown;
 | 
			
		||||
  final String threadIDs;
 | 
			
		||||
 | 
			
		||||
  CollapsableEmails(
 | 
			
		||||
      {required this.thread,
 | 
			
		||||
      required this.threadMarkdown,
 | 
			
		||||
      required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs, String? targetFiltering, required String nameOfDocument});
 | 
			
		||||
 | 
			
		||||
  get getThreads => null;
 | 
			
		||||
 | 
			
		||||
  get getAugmentRoot => null;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CollapsableEmails> createState() => _CollapsableEmailsState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		||||
  
 | 
			
		||||
  List<String> getThreads() {
 | 
			
		||||
    return widget.thread;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(body: Text("collapsable stud"));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										582
									
								
								lib/collapsableEmailsWeb.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,582 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:markdown_widget/markdown_widget.dart';
 | 
			
		||||
import 'package:markdown/markdown.dart' as md;
 | 
			
		||||
 | 
			
		||||
class CollapsableEmails extends StatefulWidget {
 | 
			
		||||
  final List<String> thread; // email id's in the form xyz@gmail.com
 | 
			
		||||
  // final List<String> threadHTML; to be replaced with the MD
 | 
			
		||||
  final List<String> threadMarkdown;
 | 
			
		||||
  final String threadIDs;
 | 
			
		||||
  final String? targetJumpNumbering;
 | 
			
		||||
  final String? targetViewspecs;
 | 
			
		||||
  final String? targetFiltering;
 | 
			
		||||
  final String? nameOfDocument;
 | 
			
		||||
 | 
			
		||||
  const CollapsableEmails({
 | 
			
		||||
    required this.thread,
 | 
			
		||||
    // required this.threadHTML,
 | 
			
		||||
    required this.threadMarkdown,
 | 
			
		||||
    required this.threadIDs,
 | 
			
		||||
    this.targetJumpNumbering,
 | 
			
		||||
    this.targetViewspecs,
 | 
			
		||||
    this.targetFiltering,
 | 
			
		||||
    this.nameOfDocument,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CollapsableEmails> createState() => _CollapsableEmailsState();
 | 
			
		||||
 | 
			
		||||
  AugmentTree? getAugmentRoot() {
 | 
			
		||||
    return _CollapsableEmailsState().getAugmentRoot();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		||||
  List<String> emailsHTML = []; //html of the emails in the thread
 | 
			
		||||
  // build attachments with the forldar name and id
 | 
			
		||||
  Set<int> _expandedEmails = {}; //open emails
 | 
			
		||||
 | 
			
		||||
  List<SerializableMessage> emailsInThread = [];
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
  List<String> hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"];
 | 
			
		||||
  Map<String, int> hirarchyDict = {
 | 
			
		||||
    "h1": 1,
 | 
			
		||||
    "h2": 2,
 | 
			
		||||
    "h3": 3,
 | 
			
		||||
    "h4": 4,
 | 
			
		||||
    "h5": 5,
 | 
			
		||||
    "h6": 6,
 | 
			
		||||
    "p": 8,
 | 
			
		||||
    "ul": 8,
 | 
			
		||||
    "li": 8,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  List<String> tagsCollected = [];
 | 
			
		||||
  List<String> allMarkdown = [];
 | 
			
		||||
  List<List<String>> sentinel = [];
 | 
			
		||||
  int level = 0;
 | 
			
		||||
  AugmentTree zoomTreeRoot = AugmentTree();
 | 
			
		||||
  // late AugmentTree currentZoomNode;
 | 
			
		||||
  late List<AugmentTree> currentZoomTree =
 | 
			
		||||
      []; // holds a list of list that holds the list of nodes on the currentzoom
 | 
			
		||||
  bool zoomOut = false;
 | 
			
		||||
  bool zoomIn = true;
 | 
			
		||||
  late List<AugmentTree> threadNodes = [];
 | 
			
		||||
  static bool leftNumbering = true;
 | 
			
		||||
  static bool rightNumbering = true;
 | 
			
		||||
  bool showWhole = false;
 | 
			
		||||
  List<AugmentTree> queryResults = []; //results of conducting filtering
 | 
			
		||||
  bool _isFilteringActive = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    threadNodes = [];
 | 
			
		||||
    currentZoomTree = [];
 | 
			
		||||
    // _markdownConverter();
 | 
			
		||||
    _serializableData(widget.threadIDs); // this
 | 
			
		||||
    _markdown2Tree(widget.threadMarkdown);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void didUpdateWidget(covariant CollapsableEmails oldWidget) {
 | 
			
		||||
    // TODO: implement didUpdateWidget
 | 
			
		||||
    super.didUpdateWidget(oldWidget);
 | 
			
		||||
    if (widget.targetJumpNumbering != null &&
 | 
			
		||||
        widget.targetJumpNumbering != oldWidget.targetJumpNumbering) {
 | 
			
		||||
      _handleJump(widget.targetJumpNumbering!);
 | 
			
		||||
    }
 | 
			
		||||
    if (widget.targetViewspecs != null &&
 | 
			
		||||
        widget.targetViewspecs != oldWidget.targetViewspecs) {
 | 
			
		||||
      _handleViewspecs(widget.targetViewspecs!);
 | 
			
		||||
    }
 | 
			
		||||
    if (widget.targetFiltering != null &&
 | 
			
		||||
        widget.targetFiltering != oldWidget.targetFiltering) {
 | 
			
		||||
      _handleFilterQuery(zoomTreeRoot, widget.targetFiltering!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<SerializableMessage> getThreads() {
 | 
			
		||||
    return emailsInThread;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AugmentTree getAugmentRoot() {
 | 
			
		||||
    return zoomTreeRoot;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _add2Tree(AugmentTree tree, md.Element node2add) {
 | 
			
		||||
    // adds node to its corresponding place
 | 
			
		||||
    AugmentTree newNode = AugmentTree();
 | 
			
		||||
    newNode.setData(node2add.textContent);
 | 
			
		||||
    newNode.ogTag = node2add.tag;
 | 
			
		||||
    // cases,
 | 
			
		||||
    //1. a node that comes is lower than the root.children last, if so it goes beneath it
 | 
			
		||||
    if (tree.children.isEmpty) {
 | 
			
		||||
      // new level to be created when totally empty
 | 
			
		||||
      tree.children.add(newNode);
 | 
			
		||||
      newNode.parent = tree;
 | 
			
		||||
    } else if (tree.children.isNotEmpty &&
 | 
			
		||||
        tree.children.last.ogTag.isNotEmpty) {
 | 
			
		||||
      if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) < // e.g. new node is h1 and old is h2, heapify
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        //have to figure out the borthers
 | 
			
		||||
        //assuming it all goes right
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          return;
 | 
			
		||||
        } else if (tree.children.last.parent == null) {
 | 
			
		||||
          // becomes the new top level
 | 
			
		||||
          for (AugmentTree brother in tree.children) {
 | 
			
		||||
            brother.parent = newNode;
 | 
			
		||||
          }
 | 
			
		||||
          tree.children = [newNode];
 | 
			
		||||
        } else {
 | 
			
		||||
          newNode.parent = tree;
 | 
			
		||||
          tree.children.add(newNode);
 | 
			
		||||
        }
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) > // go down e.g. new node is h3 and old is h2 or something
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          print("-1 ${tree.children.last.ogTag}");
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _add2Tree(tree.children.last, node2add);
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ?? -1) ==
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        tree.children.add(newNode);
 | 
			
		||||
        newNode.parent = tree;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _markdown2Tree(List<String> text) {
 | 
			
		||||
    print("started markdown2tree");
 | 
			
		||||
    for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
 | 
			
		||||
      final List<md.Node> nakedList =
 | 
			
		||||
          md.Document().parseLines(text[emailsMD].split('\n'));
 | 
			
		||||
      zoomTreeRoot = AugmentTree();
 | 
			
		||||
      for (var node in nakedList) {
 | 
			
		||||
        //maybe do an add function, but isn't this it?
 | 
			
		||||
        if (node is md.Element) {
 | 
			
		||||
          AugmentTree temp = AugmentTree();
 | 
			
		||||
          temp.data = node.textContent;
 | 
			
		||||
          temp.ogTag = node.tag;
 | 
			
		||||
          //why did  i do this???
 | 
			
		||||
          if ( hirarchyDict.containsKey(node.tag)) {
 | 
			
		||||
            _add2Tree(zoomTreeRoot, node);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      zoomTreeRoot.addNumbering();
 | 
			
		||||
      threadNodes.add(zoomTreeRoot);
 | 
			
		||||
      currentZoomTree.add(zoomTreeRoot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToChildren(int indexThread, int index) async {
 | 
			
		||||
    final target = currentZoomTree[indexThread].children[index];
 | 
			
		||||
    if (target.children.isNotEmpty) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = target;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("This child has no further children.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToChildrenFiltering(
 | 
			
		||||
      int indexThread, int index, AugmentTree node) async {
 | 
			
		||||
    final target = node;
 | 
			
		||||
    if (target.children.isNotEmpty) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = target;
 | 
			
		||||
        _isFilteringActive = false;
 | 
			
		||||
      });
 | 
			
		||||
      for (var child in target.children) {
 | 
			
		||||
        print(child.data);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      print("This child has no further children.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToParent(int indexThread) async {
 | 
			
		||||
    if (currentZoomTree[indexThread].parent != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("Already at root.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToParentFiltering(int indexThread, AugmentTree node) async {
 | 
			
		||||
    if (node.parent != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[indexThread] = node.parent!;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("Already at root.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _serializableData(String threadID) async {
 | 
			
		||||
    emailsInThread = await ApiService().threadsInSerializable(threadID);
 | 
			
		||||
    print("done thread serializable");
 | 
			
		||||
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildForZooms(int indexThread) {
 | 
			
		||||
    // index of email in thread, currentZoomTree, 
 | 
			
		||||
    // 
 | 
			
		||||
    if (!_isLoaded) {
 | 
			
		||||
      return const Center(child: CircularProgressIndicator()); // loading screen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AugmentTree
 | 
			
		||||
        currentZoomNodeForThisEmail = //each index is an email in the thread
 | 
			
		||||
        currentZoomTree[indexThread];
 | 
			
		||||
    print(currentZoomNodeForThisEmail.data);
 | 
			
		||||
    print(currentZoomNodeForThisEmail.children);
 | 
			
		||||
    print(currentZoomNodeForThisEmail.parent);
 | 
			
		||||
 | 
			
		||||
    // if (_isFilteringActive) {
 | 
			
		||||
    //   nodesToDisplay = queryResults;
 | 
			
		||||
    // } else {
 | 
			
		||||
    //   nodesToDisplay = currentZoomNodeForThisEmail.children;
 | 
			
		||||
    // }
 | 
			
		||||
    final canZoomOut = currentZoomNodeForThisEmail.parent != null;
 | 
			
		||||
 | 
			
		||||
    if (_isFilteringActive) {
 | 
			
		||||
      return ListView.builder(
 | 
			
		||||
          itemCount: queryResults.length,
 | 
			
		||||
          itemBuilder: (context, index) {
 | 
			
		||||
            AugmentTree childNode = queryResults[index];
 | 
			
		||||
            bool canZoomIn = childNode.children.isNotEmpty;
 | 
			
		||||
            return Padding(
 | 
			
		||||
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
 | 
			
		||||
              child: Material(
 | 
			
		||||
                elevation: 1,
 | 
			
		||||
                borderRadius: BorderRadius.circular(12),
 | 
			
		||||
                color: Theme.of(context).colorScheme.surface,
 | 
			
		||||
                surfaceTintColor: Theme.of(context).colorScheme.surfaceBright,
 | 
			
		||||
                child: Padding(
 | 
			
		||||
                  padding: const EdgeInsets.all(16.0),
 | 
			
		||||
                  child: Row(
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Wrap(
 | 
			
		||||
                        spacing: 4.0,
 | 
			
		||||
                        children: [
 | 
			
		||||
                          OutlinedButton(
 | 
			
		||||
                            onPressed: () => {
 | 
			
		||||
                              setState(() {
 | 
			
		||||
                                _goToParentFiltering(indexThread, childNode);
 | 
			
		||||
                                _isFilteringActive = false;
 | 
			
		||||
                              })
 | 
			
		||||
                            },
 | 
			
		||||
                            child: Icon(Icons.north_west_sharp),
 | 
			
		||||
                          ),
 | 
			
		||||
                          OutlinedButton(
 | 
			
		||||
                            onPressed: canZoomIn
 | 
			
		||||
                                ? () => _goToChildrenFiltering(
 | 
			
		||||
                                    indexThread, index, childNode)
 | 
			
		||||
                                : null,
 | 
			
		||||
                            child: Icon(Icons.south_east_sharp),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                      SizedBox(width: 12.0),
 | 
			
		||||
                      if (leftNumbering)
 | 
			
		||||
                        Padding(
 | 
			
		||||
                          padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            childNode.numbering,
 | 
			
		||||
                            style: TextStyle(
 | 
			
		||||
                                color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                        child: MarkdownBlock(
 | 
			
		||||
                          data: childNode.data,
 | 
			
		||||
                          config: MarkdownConfig
 | 
			
		||||
                              .darkConfig, // or lightConfig depending on theme
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                      if (rightNumbering)
 | 
			
		||||
                        Padding(
 | 
			
		||||
                          padding: const EdgeInsets.fromLTRB(5, 10, 5, 0),
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            childNode.numbering,
 | 
			
		||||
                            style: TextStyle(
 | 
			
		||||
                                color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ListView.builder(
 | 
			
		||||
      itemCount: currentZoomNodeForThisEmail.children.length,
 | 
			
		||||
      itemBuilder: (context, index) {
 | 
			
		||||
        final childNode = currentZoomNodeForThisEmail.children[index];
 | 
			
		||||
        final canZoomIn = childNode.children.isNotEmpty;
 | 
			
		||||
        return Padding(
 | 
			
		||||
          padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
 | 
			
		||||
          child: Material(
 | 
			
		||||
            elevation: 1,
 | 
			
		||||
            borderRadius: BorderRadius.circular(12),
 | 
			
		||||
            color: Theme.of(context).colorScheme.surface,
 | 
			
		||||
            surfaceTintColor: Theme.of(context).colorScheme.surfaceBright,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(16.0),
 | 
			
		||||
              child: Row(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Wrap(
 | 
			
		||||
                    spacing: 4.0,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed:
 | 
			
		||||
                            canZoomOut ? () => _goToParent(indexThread) : null,
 | 
			
		||||
                        child: Icon(Icons.north_west_sharp),
 | 
			
		||||
                      ),
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed: canZoomIn
 | 
			
		||||
                            ? () => _goToChildren(indexThread, index)
 | 
			
		||||
                            : null,
 | 
			
		||||
                        child: Icon(Icons.south_east_sharp),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(width: 12.0),
 | 
			
		||||
                  if (leftNumbering)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        childNode.numbering,
 | 
			
		||||
                        style:
 | 
			
		||||
                            TextStyle(color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: MarkdownBlock(
 | 
			
		||||
                      data: childNode.data,
 | 
			
		||||
                      config: MarkdownConfig
 | 
			
		||||
                          .darkConfig, // or lightConfig depending on theme
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  if (rightNumbering)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.fromLTRB(5, 10, 5, 0),
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        childNode.numbering,
 | 
			
		||||
                        style:
 | 
			
		||||
                            TextStyle(color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleJump(String queryNumbering) {
 | 
			
		||||
    print(queryNumbering);
 | 
			
		||||
    if (queryNumbering.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter a numbering to jump to.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex];
 | 
			
		||||
    final AugmentTree? foundNode =
 | 
			
		||||
        _findNodeByNumbering(rootOfCurrentEmail, queryNumbering);
 | 
			
		||||
 | 
			
		||||
    if (foundNode != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[targetEmailIndex] = foundNode; // Update the state
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Numbering "$queryNumbering" not found.')),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleViewspecs(String viewspecsQuery) {
 | 
			
		||||
    print(viewspecsQuery);
 | 
			
		||||
    if (viewspecsQuery.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter the viewspecs.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (viewspecsQuery.contains('n')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = false; // Update the state
 | 
			
		||||
        rightNumbering = false;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('m')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = true;
 | 
			
		||||
        leftNumbering = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('H')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = !leftNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('G')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = !rightNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('w')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        showWhole = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // else {
 | 
			
		||||
    //   ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
    //     SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')),
 | 
			
		||||
    //   );
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _findNodesContainingStrDFS(
 | 
			
		||||
      AugmentTree node, String query, List<AugmentTree> results) {
 | 
			
		||||
    if (node.data.contains(query)) {
 | 
			
		||||
      results.add(node);
 | 
			
		||||
    }
 | 
			
		||||
    for (var child in node.children) {
 | 
			
		||||
      _findNodesContainingStrDFS(child, query, results);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<AugmentTree> _handleFilterQuery(AugmentTree root, String query) {
 | 
			
		||||
    List<AugmentTree> results = [];
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    _findNodesContainingStrDFS(root, query, results);
 | 
			
		||||
    print(results);
 | 
			
		||||
    for (var res in results) {
 | 
			
		||||
      print(res.data);
 | 
			
		||||
    }
 | 
			
		||||
    if (results.isNotEmpty) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        queryResults = results;
 | 
			
		||||
        // currentZoomTree[targetEmailIndex] = results.first; // Update the state
 | 
			
		||||
        _isFilteringActive = true;
 | 
			
		||||
        currentZoomTree[targetEmailIndex] = root;
 | 
			
		||||
      });
 | 
			
		||||
      print(currentZoomTree);
 | 
			
		||||
    }
 | 
			
		||||
    return results;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) {
 | 
			
		||||
    //recursively finds the node you mentioned
 | 
			
		||||
    // to find the AugmentTree node corresponding to the `numbering`.
 | 
			
		||||
    if (root.numbering == numbering) {
 | 
			
		||||
      return root;
 | 
			
		||||
    }
 | 
			
		||||
    for (var child in root.children) {
 | 
			
		||||
      final found = _findNodeByNumbering(child, numbering);
 | 
			
		||||
      if (found != null) {
 | 
			
		||||
        return found;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return _isLoaded
 | 
			
		||||
        ? Column(children: [
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: ListView.builder(
 | 
			
		||||
                itemCount: emailsInThread.length,
 | 
			
		||||
                itemBuilder: (context, index) {
 | 
			
		||||
                  final isExpanded = _expandedEmails
 | 
			
		||||
                      .contains(index); //check if email is expanded
 | 
			
		||||
                  return Column(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      ListTile(
 | 
			
		||||
                        title: Text(emailsInThread[index].from),
 | 
			
		||||
                        trailing: Text(emailsInThread[index].date),
 | 
			
		||||
                        onTap: () {
 | 
			
		||||
                          setState(() {
 | 
			
		||||
                            if (isExpanded) {
 | 
			
		||||
                              _expandedEmails.remove(index);
 | 
			
		||||
                            } else {
 | 
			
		||||
                              _expandedEmails.add(index);
 | 
			
		||||
                            }
 | 
			
		||||
                          });
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                      if (isExpanded)
 | 
			
		||||
                        ConstrainedBox(
 | 
			
		||||
                          constraints: BoxConstraints(
 | 
			
		||||
                            minHeight: 100,
 | 
			
		||||
                            maxHeight: MediaQuery.of(context).size.height * 0.6,
 | 
			
		||||
                          ),
 | 
			
		||||
                          child: _buildForZooms(index), //show the tree
 | 
			
		||||
                        ),
 | 
			
		||||
                      Divider(),
 | 
			
		||||
                    ],
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ])
 | 
			
		||||
        : const Center(child: CircularProgressIndicator());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,4 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
import 'package:flutter_html/flutter_html.dart';
 | 
			
		||||
 | 
			
		||||
class ContactsPage extends StatefulWidget {
 | 
			
		||||
  const ContactsPage({super.key});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										295
									
								
								lib/email.dart
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,57 +1,227 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
import 'package:markdown/markdown.dart' as md;
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'emailView.dart';
 | 
			
		||||
import 'Compose.dart';
 | 
			
		||||
 | 
			
		||||
class EmailListScreen extends StatelessWidget {
 | 
			
		||||
class EmailListScreen extends StatefulWidget {
 | 
			
		||||
  final List<GetThreadResponse> emails;
 | 
			
		||||
  final Future<List<String>> Function(List<String>, String) getEmailContent;
 | 
			
		||||
  final String folder;
 | 
			
		||||
  final GlobalKey<_EmailListScreenState> key;
 | 
			
		||||
  final Function(List<GetThreadResponse>)? onSelectionChanged;
 | 
			
		||||
 | 
			
		||||
  EmailListScreen({
 | 
			
		||||
    required this.key,
 | 
			
		||||
    required this.emails,
 | 
			
		||||
    required this.getEmailContent,
 | 
			
		||||
    required this.folder,
 | 
			
		||||
    this.onSelectionChanged,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _EmailListScreenState createState() => _EmailListScreenState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmailListScreenState extends State<EmailListScreen>
 | 
			
		||||
    with TickerProviderStateMixin {
 | 
			
		||||
  late List<bool> selectStates; // for checkboxes if its selected or not
 | 
			
		||||
  late List<GetThreadResponse> selectedEmails =
 | 
			
		||||
      []; // holds the emails that are selected i.e. the emails that got the checkbox on
 | 
			
		||||
  final Set<int> _hoveredRows = {}; //the row that is being hovered over atm
 | 
			
		||||
  bool bulkSelectMenu = false;
 | 
			
		||||
  final GlobalKey<EmailPageState> _emailPageKey = GlobalKey<EmailPageState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    selectStates = List<bool>.filled(widget.emails.length, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void didUpdateWidget(covariant EmailListScreen oldWidget) {
 | 
			
		||||
    super.didUpdateWidget(oldWidget);
 | 
			
		||||
    if (oldWidget.emails.length != widget.emails.length) {
 | 
			
		||||
      selectStates = List<bool>.filled(widget.emails.length, false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool selectAllChecks(bool selectionType) {
 | 
			
		||||
    //perhaps it should return a list of the selected
 | 
			
		||||
    setState(() {
 | 
			
		||||
      selectedEmails = [];
 | 
			
		||||
      if (selectionType) {
 | 
			
		||||
        bulkSelectMenu = true;
 | 
			
		||||
        for (int email = 0; email < selectStates.length; email++) {
 | 
			
		||||
          selectStates[email] = selectionType;
 | 
			
		||||
          selectedEmails.add(widget.emails[email]);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        for (int email = 0; email < selectStates.length; email++) {
 | 
			
		||||
          selectStates[email] = selectionType;
 | 
			
		||||
        }
 | 
			
		||||
        selectedEmails = [];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    widget.onSelectionChanged?.call(selectedEmails);
 | 
			
		||||
    printTheSelected();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool markAsRead(bool read) {
 | 
			
		||||
    print("markasread $read");
 | 
			
		||||
    setState(() {
 | 
			
		||||
      if (read) {
 | 
			
		||||
        //read
 | 
			
		||||
        for (int email = 0; email < selectedEmails.length; email++) {
 | 
			
		||||
          selectedEmails[email].seen = read;
 | 
			
		||||
          ApiService()
 | 
			
		||||
              .markAsSeen(selectedEmails[email].id); //the remote or .json
 | 
			
		||||
          print(selectedEmails[email].id);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        //unread
 | 
			
		||||
        for (int email = 0; email < selectedEmails.length; email++) {
 | 
			
		||||
          selectedEmails[email].seen = read;
 | 
			
		||||
          ApiService()
 | 
			
		||||
              .markAsUnseen(selectedEmails[email].id); //the remote or .json
 | 
			
		||||
          print(selectedEmails[email].id);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool moveOfSelected(String destinyFolder) {
 | 
			
		||||
    //this should be called from a widget
 | 
			
		||||
    print("move of folder");
 | 
			
		||||
    setState(() {
 | 
			
		||||
      for (int email = 0; email < selectedEmails.length; email++) {
 | 
			
		||||
        ApiService().moveEmail(
 | 
			
		||||
            widget.folder, selectedEmails[email].id.toString(), destinyFolder);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Widget moveOfFolderWidget()
 | 
			
		||||
 | 
			
		||||
  List<GetThreadResponse> listOfSelectedThreads() {
 | 
			
		||||
    return selectedEmails;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void printTheSelected() {
 | 
			
		||||
    for (int i = 0; i < selectedEmails.length; i++) {
 | 
			
		||||
      print(selectedEmails[i].subject);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  EmailListScreen(
 | 
			
		||||
      {required this.emails,
 | 
			
		||||
      required this.getEmailContent,
 | 
			
		||||
      required this.folder});
 | 
			
		||||
//fix the email list
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: ListView.separated(
 | 
			
		||||
        itemCount: emails.length,
 | 
			
		||||
        itemCount: widget.emails.length,
 | 
			
		||||
        itemBuilder: (context, index) {
 | 
			
		||||
          final email = emails[index];
 | 
			
		||||
          return ListTile(
 | 
			
		||||
            title: Text(email.from_name,
 | 
			
		||||
                style: TextStyle(fontWeight: FontWeight.bold)),
 | 
			
		||||
            subtitle: Column(
 | 
			
		||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
              children: [Text(email.subject)],
 | 
			
		||||
            ),
 | 
			
		||||
            trailing: Text(email.date.toString()),
 | 
			
		||||
            onTap: () async {
 | 
			
		||||
              List<String> emailContent = //  list of the html
 | 
			
		||||
                  await getEmailContent(email.messages, folder);
 | 
			
		||||
              // print("this is what email.messages look like in email.dart ${email.messages}");
 | 
			
		||||
              // List<String> emailIds = email.messages;
 | 
			
		||||
          Color seenColour;
 | 
			
		||||
          final email = widget.emails[index];
 | 
			
		||||
          if (email.seen) {
 | 
			
		||||
            seenColour = ThemeData().highlightColor;
 | 
			
		||||
          } else {
 | 
			
		||||
            seenColour = Colors.transparent;
 | 
			
		||||
          }
 | 
			
		||||
          return MouseRegion(
 | 
			
		||||
            onEnter: (_) => setState(() => _hoveredRows.add(index)),
 | 
			
		||||
            onExit: (_) => setState(() => _hoveredRows.remove(index)),
 | 
			
		||||
            child: ListTile(
 | 
			
		||||
              leading: Checkbox(
 | 
			
		||||
                value: selectStates[index],
 | 
			
		||||
                onChanged: (bool? value) {
 | 
			
		||||
                  setState(() {
 | 
			
		||||
                    //works great
 | 
			
		||||
                    selectStates[index] = value ?? false;
 | 
			
		||||
 | 
			
		||||
              print(email.messages); //email ids of the thread
 | 
			
		||||
              Navigator.push(
 | 
			
		||||
                context,
 | 
			
		||||
                MaterialPageRoute(
 | 
			
		||||
                  // could call collapsable and inside collable each calls email view?
 | 
			
		||||
                  builder: (context) => EmailView(
 | 
			
		||||
                    emailContent: emailContent,
 | 
			
		||||
                    from: email.from_address,
 | 
			
		||||
                    name: email.from_name,
 | 
			
		||||
                    to: email.to.toString(),
 | 
			
		||||
                    subject: email.subject,
 | 
			
		||||
                    date: email.date.toString(),
 | 
			
		||||
                    id: email.id.toString(), //i think this is thread id?
 | 
			
		||||
                    messages: email.messages,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              );
 | 
			
		||||
            },
 | 
			
		||||
                    setState(() {
 | 
			
		||||
                      if (value!) {
 | 
			
		||||
                        selectedEmails.add(widget.emails[index]);
 | 
			
		||||
                        //here i must update the other side
 | 
			
		||||
                        _emailPageKey.currentState?.getListOfSelected();
 | 
			
		||||
                      } else {
 | 
			
		||||
                        selectedEmails.remove(widget.emails[index]);
 | 
			
		||||
                        _emailPageKey.currentState?.getListOfSelected();
 | 
			
		||||
                      }
 | 
			
		||||
                      widget.onSelectionChanged?.call(selectedEmails);
 | 
			
		||||
                      print(selectedEmails);
 | 
			
		||||
                    });
 | 
			
		||||
                  });
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
              title: Text(email.from_name,
 | 
			
		||||
                  style: TextStyle(fontWeight: FontWeight.bold)),
 | 
			
		||||
              subtitle: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [Text(email.subject)],
 | 
			
		||||
              ),
 | 
			
		||||
              trailing: _hoveredRows.contains(index)
 | 
			
		||||
                  ? Row(
 | 
			
		||||
                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          icon: Icon(Icons.mark_email_read_outlined),
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            // mark email as read
 | 
			
		||||
                            setState(() {
 | 
			
		||||
                              widget.emails[index].seen = true;
 | 
			
		||||
                              ApiService().markAsSeen(email.id);
 | 
			
		||||
                            });
 | 
			
		||||
                          },
 | 
			
		||||
                        ),
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          icon: Icon(Icons.delete_outline),
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            // delete email
 | 
			
		||||
                            ApiService().deleteEmail(widget.folder, email.id);
 | 
			
		||||
                          },
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    )
 | 
			
		||||
                  : Text(email.date.toString()),
 | 
			
		||||
              hoverColor: Colors.transparent,
 | 
			
		||||
              tileColor: seenColour,
 | 
			
		||||
              onTap: () async {
 | 
			
		||||
                List<String> emailContent = //  list of the html
 | 
			
		||||
                    await widget.getEmailContent(email.messages, widget.folder);
 | 
			
		||||
                // print("thread id? $email.id"); yes
 | 
			
		||||
                print(email.messages); //email ids of the thread
 | 
			
		||||
                if (widget.folder == "Drafts") {
 | 
			
		||||
                  print("IN DRAFTS MOVE THE CONTENT TO THE WRITING THING");
 | 
			
		||||
                  //open the compose
 | 
			
		||||
                  OverlayService _thisInstance = OverlayService();
 | 
			
		||||
                  _thisInstance.draftID = email;
 | 
			
		||||
                  _thisInstance.showPersistentWidget(context);
 | 
			
		||||
                } else {
 | 
			
		||||
                  // print(email)
 | 
			
		||||
                  Navigator.push(
 | 
			
		||||
                    context,
 | 
			
		||||
                    MaterialPageRoute(
 | 
			
		||||
                      // could call collapsable and inside collable each calls email view?
 | 
			
		||||
                      builder: (context) => EmailView(
 | 
			
		||||
                        emailContent: emailContent,
 | 
			
		||||
                        from: email.from_address,
 | 
			
		||||
                        name: email.from_name,
 | 
			
		||||
                        to: email.to.toString(),
 | 
			
		||||
                        subject: email.subject,
 | 
			
		||||
                        date: email.date.toString(),
 | 
			
		||||
                        id: email.id.toString(), //i think this is thread id?
 | 
			
		||||
                        messages: email.messages,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  );
 | 
			
		||||
                  ApiService().markAsSeen(email.id);
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
        separatorBuilder: (context, index) => Divider(),
 | 
			
		||||
| 
						 | 
				
			
			@ -62,10 +232,12 @@ class EmailListScreen extends StatelessWidget {
 | 
			
		|||
 | 
			
		||||
// ignore: must_be_immutable
 | 
			
		||||
class EmailPage extends StatefulWidget {
 | 
			
		||||
  EmailPage({Key? key}) : super(key: key);
 | 
			
		||||
  String selectedFolder = "INBOX"; //starter
 | 
			
		||||
  int offset = 0;
 | 
			
		||||
  int page = 1;
 | 
			
		||||
  final Function(List<GetThreadResponse>)? onSelectionChanged;
 | 
			
		||||
 | 
			
		||||
  EmailPage({Key? key, this.onSelectionChanged}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  EmailPageState createState() => EmailPageState();
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +250,9 @@ class EmailPageState extends State<EmailPage> {
 | 
			
		|||
  int page = 1;
 | 
			
		||||
  bool isBackDisabled = false;
 | 
			
		||||
 | 
			
		||||
  final GlobalKey<_EmailListScreenState> emailListKey =
 | 
			
		||||
      GlobalKey<_EmailListScreenState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +261,7 @@ class EmailPageState extends State<EmailPage> {
 | 
			
		|||
    _fetchEmails();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<GetThreadResponse> get getEmails => emails;
 | 
			
		||||
  String getPage() => widget.page.toString();
 | 
			
		||||
  bool get backDisabled => isBackDisabled;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +292,6 @@ class EmailPageState extends State<EmailPage> {
 | 
			
		|||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // print(currentPage);
 | 
			
		||||
    print(widget.page);
 | 
			
		||||
    _fetchEmails();
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +299,7 @@ class EmailPageState extends State<EmailPage> {
 | 
			
		|||
  void _fetchEmails() async {
 | 
			
		||||
    try {
 | 
			
		||||
      List<GetThreadResponse> fetchedEmails = await apiService
 | 
			
		||||
          .fetchEmailsFromFolder(widget.selectedFolder, widget.offset);
 | 
			
		||||
          .fetchEmailsFromFolderReversed(widget.selectedFolder, widget.offset);
 | 
			
		||||
      if (!mounted) return;
 | 
			
		||||
 | 
			
		||||
      setState(() {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,14 +310,36 @@ class EmailPageState extends State<EmailPage> {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool selectAllEmails(bool selectionType) {
 | 
			
		||||
    emailListKey.currentState?.selectAllChecks(selectionType);
 | 
			
		||||
    return selectionType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool markSelectedAsRead(bool selectionType) {
 | 
			
		||||
    emailListKey.currentState?.markAsRead(selectionType);
 | 
			
		||||
    return selectionType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool moveSelectedOfFolder(String folder) {
 | 
			
		||||
    emailListKey.currentState?.moveOfSelected(folder);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<GetThreadResponse> getListOfSelected() {
 | 
			
		||||
    return emailListKey.currentState!.listOfSelectedThreads();
 | 
			
		||||
  }
 | 
			
		||||
  // return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: EmailListScreen(
 | 
			
		||||
        emails: emails,
 | 
			
		||||
        getEmailContent: apiService.fetchEmailContent,
 | 
			
		||||
        folder: widget.selectedFolder, //try to grab from it directly
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
        body: EmailListScreen(
 | 
			
		||||
      key: emailListKey,
 | 
			
		||||
      emails: emails,
 | 
			
		||||
      // getEmailContent: apiService.fetchEmailContent,
 | 
			
		||||
      getEmailContent: apiService.fetchMarkdownContent,
 | 
			
		||||
      folder: widget.selectedFolder, //try to grab from it directly
 | 
			
		||||
      onSelectionChanged: widget.onSelectionChanged,
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								lib/emailView.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export 'emailViewStub.dart'
 | 
			
		||||
  if (dart.library.io) 'emailViewAndroid.dart'
 | 
			
		||||
  if (dart.library.js_interop) 'emailViewWeb.dart';
 | 
			
		||||
							
								
								
									
										120
									
								
								lib/emailViewAndroid.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,120 @@
 | 
			
		|||
import 'package:crab_ui/augment.dart';
 | 
			
		||||
import 'package:crab_ui/collapsableEmailsAndroid.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
// import 'dart:ui_web' as ui;
 | 
			
		||||
// import 'augment.dart';
 | 
			
		||||
// // import 'dart:js_interop' as js; //eventually for manipulating css
 | 
			
		||||
// import 'package:pointer_interceptor/pointer_interceptor.dart';
 | 
			
		||||
// import 'collapsableEmails.dart';
 | 
			
		||||
// import 'api_service.dart';
 | 
			
		||||
 | 
			
		||||
class EmailView extends StatefulWidget {
 | 
			
		||||
  final List<String> emailContent;
 | 
			
		||||
  final String from;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String to;
 | 
			
		||||
  final String subject;
 | 
			
		||||
  final String date;
 | 
			
		||||
  final String id;
 | 
			
		||||
  final List<String> messages;
 | 
			
		||||
 | 
			
		||||
  const EmailView({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.emailContent,
 | 
			
		||||
    required this.from,
 | 
			
		||||
    required this.name, // tf is name
 | 
			
		||||
    required this.to,
 | 
			
		||||
    required this.subject,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.messages,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _EmailViewState createState() => _EmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmailViewState extends State<EmailView> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    // AugmentClasses.handleJump(spanId);
 | 
			
		||||
  }
 | 
			
		||||
  void _viewSpecs(String command){
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _filteringQuery(String query){
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(widget.name),
 | 
			
		||||
      ),
 | 
			
		||||
      body: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            EmailToolbar(
 | 
			
		||||
              onButtonPressed: () => {}, 
 | 
			
		||||
              onJumpToNumbering: _scrollToNumber,
 | 
			
		||||
              onViewspecs: _viewSpecs,
 | 
			
		||||
              onFiltering: _filteringQuery,
 | 
			
		||||
              emails: widget.messages, subject: '', rootAugment: null,
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    widget.subject,
 | 
			
		||||
                    style: TextStyle(fontSize: 15),
 | 
			
		||||
                    overflow: TextOverflow.visible,
 | 
			
		||||
                    softWrap: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  'from ${widget.name}',
 | 
			
		||||
                  style: TextStyle(fontSize: 8),
 | 
			
		||||
                ),
 | 
			
		||||
                Text(
 | 
			
		||||
                  '<${widget.from}>',
 | 
			
		||||
                  style: TextStyle(fontSize: 8),
 | 
			
		||||
                ),
 | 
			
		||||
                Spacer(),
 | 
			
		||||
                Text(
 | 
			
		||||
                  widget.date,
 | 
			
		||||
                  textAlign: TextAlign.right,
 | 
			
		||||
                )
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  'to ${widget.to.toString()}',
 | 
			
		||||
                  style: TextStyle(fontSize: 8),
 | 
			
		||||
                )
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: CollapsableEmails(
 | 
			
		||||
                thread: widget.messages, 
 | 
			
		||||
                threadMarkdown: widget.emailContent, 
 | 
			
		||||
                threadIDs: widget.id,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ) 
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								lib/emailViewStub.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmailView extends StatefulWidget {
 | 
			
		||||
  final List<String> emailContent;
 | 
			
		||||
  final String from;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String to;
 | 
			
		||||
  final String subject;
 | 
			
		||||
  final String date;
 | 
			
		||||
  final String id;
 | 
			
		||||
  final List<String> messages;
 | 
			
		||||
 | 
			
		||||
  const EmailView({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.emailContent,
 | 
			
		||||
    required this.from,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.to,
 | 
			
		||||
    required this.subject,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.messages,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _EmailViewState createState() => _EmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmailViewState extends State<EmailView> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
    body: Center(
 | 
			
		||||
      child: Text(" emailview stub, not supported")
 | 
			
		||||
    )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										159
									
								
								lib/emailViewWeb.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,159 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'dart:ui_web' as ui;
 | 
			
		||||
import 'augment.dart';
 | 
			
		||||
import 'collapsableEmails.dart';
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
 | 
			
		||||
class EmailView extends StatefulWidget {
 | 
			
		||||
  final List<String> emailContent;
 | 
			
		||||
  final String from;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String to;
 | 
			
		||||
  final String subject;
 | 
			
		||||
  final String date;
 | 
			
		||||
  final String id;
 | 
			
		||||
  final List<String> messages;
 | 
			
		||||
 | 
			
		||||
  const EmailView({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.emailContent,
 | 
			
		||||
    required this.from,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.to,
 | 
			
		||||
    required this.subject,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.messages,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _EmailViewState createState() => _EmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmailViewState extends State<EmailView> {
 | 
			
		||||
  //html css rendering thing
 | 
			
		||||
  late Key iframeKey;
 | 
			
		||||
  late String currentContent;
 | 
			
		||||
  late String viewTypeId; //make this a list too???
 | 
			
		||||
  // TextEditingController _jumpController = TextEditingController();
 | 
			
		||||
  late EmailToolbar toolbarInstance = EmailToolbar(
 | 
			
		||||
    onJumpToNumbering: _handleJumpRequest,
 | 
			
		||||
    onViewspecs: _handleViewspecsRequest,
 | 
			
		||||
    onButtonPressed: () => {print("email tool bar pressed")},
 | 
			
		||||
    onFiltering: _handleFiltering,
 | 
			
		||||
    emails: widget.messages,
 | 
			
		||||
    subject: widget.subject,
 | 
			
		||||
    rootAugment: localCollapsable.getAugmentRoot(),
 | 
			
		||||
  );
 | 
			
		||||
  
 | 
			
		||||
  late CollapsableEmails localCollapsable = CollapsableEmails(
 | 
			
		||||
    //change here
 | 
			
		||||
    thread: widget.messages, //this wont work in serializable
 | 
			
		||||
    // threadHTML: widget.emailContent, // old html
 | 
			
		||||
    threadMarkdown: widget.emailContent,
 | 
			
		||||
    threadIDs: widget.id,
 | 
			
		||||
    targetJumpNumbering: _targetJumpNumbering,
 | 
			
		||||
    targetViewspecs: _targetViewspecs,
 | 
			
		||||
    targetFiltering: _queryFiltering,
 | 
			
		||||
    nameOfDocument: widget.subject,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  final hardcodedMarkers = [
 | 
			
		||||
    {'id': 'marker1', 'x': 50, 'y': 100},
 | 
			
		||||
    {'id': 'marker2', 'x': 150, 'y': 200},
 | 
			
		||||
    {'id': 'marker3', 'x': 250, 'y': 300},
 | 
			
		||||
  ];
 | 
			
		||||
  String? _targetJumpNumbering;
 | 
			
		||||
  String? _targetViewspecs;
 | 
			
		||||
  String? _queryFiltering;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    print("thread id? ${widget.id}");
 | 
			
		||||
    List<String> currentContent = widget
 | 
			
		||||
        .emailContent; //html of the email/ actually entire thread, gives me little space to play in between
 | 
			
		||||
    // i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse
 | 
			
		||||
    // _registerViewFactory(currentContent);
 | 
			
		||||
    print("email content in Collapsable ${widget.emailContent}");
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    AugmentClasses.handleJump(spanId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleJumpRequest(String numbering) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _targetJumpNumbering = numbering;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleViewspecsRequest(String viewspecsCommand) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _targetViewspecs = viewspecsCommand;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleFiltering(String query) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _queryFiltering = query;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    ApiService.currThreadID = widget.id;
 | 
			
		||||
    // AugmentClasses localAugment = AugmentClasses(localCollapsable);
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(widget.name),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Stack(
 | 
			
		||||
          children: [
 | 
			
		||||
            Column(
 | 
			
		||||
              children: [
 | 
			
		||||
                toolbarInstance,
 | 
			
		||||
                Row(
 | 
			
		||||
                  // title of email
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      widget.subject,
 | 
			
		||||
                      style: TextStyle(fontSize: 30),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      'from ${widget.name}',
 | 
			
		||||
                      style: TextStyle(fontSize: 18),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Text(
 | 
			
		||||
                      '<${widget.from}>',
 | 
			
		||||
                      style: TextStyle(fontSize: 18),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Spacer(),
 | 
			
		||||
                    Text(
 | 
			
		||||
                      '${widget.date}',
 | 
			
		||||
                      textAlign: TextAlign.right,
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                // TODO: make a case where if one of these is the user's email it just says me :)))))
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      'to ${widget.to.toString()}',
 | 
			
		||||
                      style: TextStyle(fontSize: 15),
 | 
			
		||||
                    )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: localCollapsable,
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										221
									
								
								lib/login.dart
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,25 +1,32 @@
 | 
			
		|||
import 'dart:convert';
 | 
			
		||||
// import 'package:crab_ui/api_service.dart';
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'home_page.dart';
 | 
			
		||||
// import 'home_page.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
// import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
// import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'package:flutter/services.dart' show rootBundle;
 | 
			
		||||
 | 
			
		||||
class AuthService {
 | 
			
		||||
class AuthService extends ChangeNotifier {
 | 
			
		||||
  Future<bool> isUserLoggedIn() async {
 | 
			
		||||
    // ApiService.ip = '127.0.0.1';
 | 
			
		||||
    // ApiService.port = '3001';
 | 
			
		||||
    // print("setted up");
 | 
			
		||||
 | 
			
		||||
    // return true;
 | 
			
		||||
    try {
 | 
			
		||||
      final response =
 | 
			
		||||
          await http.get(Uri.http('localhost:6823', 'read-config'));
 | 
			
		||||
      // await http.get(Uri.parse('http://localhost:6823/read-config'));
 | 
			
		||||
          await http.get(Uri.http('127.0.0.1:6767', 'login_check'));
 | 
			
		||||
 | 
			
		||||
      print(response.statusCode);
 | 
			
		||||
      print(response.body);
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        final data = jsonDecode(response.body);
 | 
			
		||||
        // return data['config'];
 | 
			
		||||
        // print(data['state']);
 | 
			
		||||
        // return true;
 | 
			
		||||
        try {
 | 
			
		||||
          var url = Uri.http('${data['ip']}:${data['port']}', 'is_logged_in');
 | 
			
		||||
          var response = await http.get(url);
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +90,7 @@ class LoginPage extends StatefulWidget {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
class SplashScreen extends StatefulWidget {
 | 
			
		||||
  //entry point
 | 
			
		||||
  @override
 | 
			
		||||
  _SplashScreenState createState() => _SplashScreenState();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -92,19 +100,31 @@ class _SplashScreenState extends State<SplashScreen> {
 | 
			
		|||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _checkLoginStatus();
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
      _checkLoginStatus();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _checkLoginStatus() async {
 | 
			
		||||
    // SharedPreferences prefs = await SharedPreferences.getInstance();
 | 
			
		||||
    // print(prefs);
 | 
			
		||||
    // bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
 | 
			
		||||
    bool isLoggedIn = await _authService.isUserLoggedIn();
 | 
			
		||||
    print("is loogeed in $isLoggedIn");
 | 
			
		||||
    if (isLoggedIn) {
 | 
			
		||||
      Navigator.pushReplacementNamed(context, '/home');
 | 
			
		||||
    } else {
 | 
			
		||||
      Navigator.pushReplacementNamed(context, '/login');
 | 
			
		||||
    try {
 | 
			
		||||
      final response =
 | 
			
		||||
          await http.get(Uri.parse('http://127.0.0.1:6767/login_check'));
 | 
			
		||||
      print(response.statusCode);
 | 
			
		||||
      print(response.body);
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        final data = jsonDecode(response.body);
 | 
			
		||||
        print(data['state']);
 | 
			
		||||
        if (data['state']) {
 | 
			
		||||
          context.go("/home");
 | 
			
		||||
        } else {
 | 
			
		||||
          context.go("/login");
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        context.go("/login");
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print("caught in checkloginstatus in login $e");
 | 
			
		||||
      context.go("/login");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +132,7 @@ class _SplashScreenState extends State<SplashScreen> {
 | 
			
		|||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        body: Center(child: CircularProgressIndicator()),
 | 
			
		||||
        body: Center(child: CircularProgressIndicator()), //nothing happens
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -126,13 +146,10 @@ class _LoginPageState extends State<LoginPage> {
 | 
			
		|||
  final TextEditingController _emailController = TextEditingController();
 | 
			
		||||
  final TextEditingController _passwordController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  // final ConfigManager _configManager =
 | 
			
		||||
  //     ConfigManager("${Directory.current.parent}../crabmail2.conf");
 | 
			
		||||
  // Key to identify the form
 | 
			
		||||
  final _formKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  Future<bool> setIp(String ip) async {
 | 
			
		||||
    // _configManager.setField("api_addr", ip);
 | 
			
		||||
    //this is not done :sob: :skull:
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -141,31 +158,16 @@ class _LoginPageState extends State<LoginPage> {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> login() async {
 | 
			
		||||
    bool result = await _handleLogin();
 | 
			
		||||
    if (result) {
 | 
			
		||||
      Navigator.pushReplacementNamed(context, '/home');
 | 
			
		||||
    var result = await _handleLogin();
 | 
			
		||||
    if (result[0]) {
 | 
			
		||||
      ApiService.ip = result[1];
 | 
			
		||||
      ApiService.port = result[2];
 | 
			
		||||
      context.go('/home');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // Future<bool> _checkConfiguration() async {
 | 
			
		||||
  //   return false;
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // void checkLogin() async {
 | 
			
		||||
  //   try {
 | 
			
		||||
  //     var url = Uri.http('127.0.0.1:3001', 'is_logged_in');
 | 
			
		||||
  //     var response = await http.get(url);
 | 
			
		||||
  //     print(response.body);
 | 
			
		||||
  //     if (response.statusCode == 200) {
 | 
			
		||||
  //       print('all good on the west');
 | 
			
		||||
  //     }
 | 
			
		||||
  //   } catch (e) {
 | 
			
		||||
  //     print(e);
 | 
			
		||||
  //   }
 | 
			
		||||
  //   // bool isLoggedIn = await _authService.isUserLoggedIn();
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // Function to handle login action
 | 
			
		||||
  Future<bool> _handleLogin() async {
 | 
			
		||||
  Future<List> _handleLogin() async {
 | 
			
		||||
    if (_formKey.currentState!.validate()) {
 | 
			
		||||
      // Perform login action (e.g., authenticate with backend)
 | 
			
		||||
      String ip = _ipController.text;
 | 
			
		||||
| 
						 | 
				
			
			@ -179,96 +181,64 @@ class _LoginPageState extends State<LoginPage> {
 | 
			
		|||
      print(ip);
 | 
			
		||||
      print(port);
 | 
			
		||||
 | 
			
		||||
      String baseUrl = "http://$ip:$port";
 | 
			
		||||
      print("baseurl " + baseUrl);
 | 
			
		||||
      print(baseUrl);
 | 
			
		||||
      try {
 | 
			
		||||
        final response =
 | 
			
		||||
            await http.get(Uri.parse('http://localhost:6823/read-config'));
 | 
			
		||||
        Map<String, String> requestBody = {
 | 
			
		||||
          "user": email,
 | 
			
		||||
          "password": password,
 | 
			
		||||
          "ip": ip,
 | 
			
		||||
          "port": port,
 | 
			
		||||
        };
 | 
			
		||||
        var url = Uri.http("$ip:6767", "login");
 | 
			
		||||
 | 
			
		||||
        final response = await http.post(url,
 | 
			
		||||
            headers: {
 | 
			
		||||
              'Content-Type': 'application/json',
 | 
			
		||||
            },
 | 
			
		||||
            body: jsonEncode(
 | 
			
		||||
                requestBody)); //keep the port but change the ip to the server that will process it?
 | 
			
		||||
        print(response.statusCode);
 | 
			
		||||
        print(response.body);
 | 
			
		||||
        if (response.statusCode == 200) {
 | 
			
		||||
          final data = jsonDecode(response.body);
 | 
			
		||||
          // return data['config'];
 | 
			
		||||
          if (data["state"] == true) {
 | 
			
		||||
            try {
 | 
			
		||||
              final response = await http
 | 
			
		||||
                  .get(Uri.parse('http://127.0.0.1:6767/login_check'));
 | 
			
		||||
              print(response.statusCode);
 | 
			
		||||
              print(response.body);
 | 
			
		||||
              if (response.statusCode == 200) {
 | 
			
		||||
                final data = jsonDecode(response.body);
 | 
			
		||||
                print(data['state']);
 | 
			
		||||
                if (data['state']) {
 | 
			
		||||
                  context.go("/home");
 | 
			
		||||
                } else {
 | 
			
		||||
                  context.go("/login");
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                context.go("/login");
 | 
			
		||||
              }
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
              print("caught in checkloginstatus in login $e");
 | 
			
		||||
              context.go("/login");
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return [true, ip, port];
 | 
			
		||||
        }
 | 
			
		||||
        return [false];
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print("caught in catch");
 | 
			
		||||
        print(e);
 | 
			
		||||
        return false;
 | 
			
		||||
        return [false];
 | 
			
		||||
      }
 | 
			
		||||
      Map<String, dynamic> updates = {
 | 
			
		||||
        // "username": email,
 | 
			
		||||
        // "password": password,
 | 
			
		||||
        "ip": ip,
 | 
			
		||||
        "port": port,
 | 
			
		||||
      };
 | 
			
		||||
      print("past");
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        final sending = await http.post(
 | 
			
		||||
            Uri.parse('http://localhost:6823/update-config'),
 | 
			
		||||
            headers: {'Content-Type': "application/json"},
 | 
			
		||||
            body: jsonEncode(updates));
 | 
			
		||||
        print("sending");
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print(e);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      try {
 | 
			
		||||
        // String status = await http.post(Uri.parse(''))
 | 
			
		||||
        var url_log = Uri.http('$ip:$port', 'log_in');
 | 
			
		||||
        Map<String, dynamic> filteredData = {
 | 
			
		||||
          "email": email,
 | 
			
		||||
          "password": password,
 | 
			
		||||
          // 'email': updates['username'],
 | 
			
		||||
          // 'password': updates['password']
 | 
			
		||||
        };
 | 
			
		||||
        print(filteredData);
 | 
			
		||||
        var status = await http.post(
 | 
			
		||||
          url_log,
 | 
			
		||||
          headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: jsonEncode(filteredData),
 | 
			
		||||
        );
 | 
			
		||||
        if (status.statusCode == 200) {
 | 
			
		||||
          print('response status ${status.body}');
 | 
			
		||||
          if (status.body == "Successful log in") {
 | 
			
		||||
            ApiService.ip = ip;
 | 
			
		||||
            ApiService.port = port;
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print(e);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      print("after");
 | 
			
		||||
      return false;
 | 
			
		||||
      // final content = await _authService.readConfFile();
 | 
			
		||||
      // final config = await _authService.parseConfFile(content);
 | 
			
		||||
      // print("BASE URL ${config["base_url"]}");
 | 
			
		||||
      // print("api address ${config["api_addr"]}");
 | 
			
		||||
      // print(config);
 | 
			
		||||
      // const url = ''
 | 
			
		||||
      // Clear the input fields
 | 
			
		||||
      // _ipController.clear();
 | 
			
		||||
      // _portController.clear();
 | 
			
		||||
      // _emailController.clear();
 | 
			
		||||
      // _passwordController.clear();
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
    return [false];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    // try {
 | 
			
		||||
    //   _configManager.loadConfig();
 | 
			
		||||
    //   print(_configManager.getField('base_url'));
 | 
			
		||||
    // } catch (e) {
 | 
			
		||||
    //   print("broke at build $e");
 | 
			
		||||
    // }
 | 
			
		||||
    // _configManager.
 | 
			
		||||
    _ipController.text = "127.0.0.1";
 | 
			
		||||
    _portController.text = "3001";
 | 
			
		||||
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
| 
						 | 
				
			
			@ -303,14 +273,6 @@ class _LoginPageState extends State<LoginPage> {
 | 
			
		|||
                            return 'Please enter your ip';
 | 
			
		||||
                          }
 | 
			
		||||
                        },
 | 
			
		||||
                        // onSaved: (value) async {
 | 
			
		||||
                        //   final content = await _authService.readConfFile();
 | 
			
		||||
                        //   final config =
 | 
			
		||||
                        //       await _authService.parseConfFile(content);
 | 
			
		||||
                        //   print("BASE URL ${config["base_url"]}");
 | 
			
		||||
                        //   print("api address ${config["api_addr"]}");
 | 
			
		||||
                        //   //TODO: call a function to set the field ip in conf
 | 
			
		||||
                        // },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Container(
 | 
			
		||||
| 
						 | 
				
			
			@ -389,17 +351,6 @@ class _LoginPageState extends State<LoginPage> {
 | 
			
		|||
                        child: const Text('Login'),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    // SizedBox(
 | 
			
		||||
                    //   width: 200,
 | 
			
		||||
                    //   child: ElevatedButton(
 | 
			
		||||
                    //     // onPressed: checkLogin,
 | 
			
		||||
                    //     onPressed: () async {
 | 
			
		||||
                    //       await _authService.isUserLoggedIn();
 | 
			
		||||
                    //       // print(result);
 | 
			
		||||
                    //     },
 | 
			
		||||
                    //     child: const Text('checker'),
 | 
			
		||||
                    //   ),
 | 
			
		||||
                    // )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,18 @@
 | 
			
		|||
import 'package:crab_ui/contact.dart';
 | 
			
		||||
import 'package:crab_ui/email.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'home_page.dart';
 | 
			
		||||
import 'login.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'routingHandler.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
  runApp(HyM());
 | 
			
		||||
  runApp(ChangeNotifierProvider(
 | 
			
		||||
    create: (context) => AuthService(),
 | 
			
		||||
    child: HyM(),
 | 
			
		||||
  ));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class HyM extends StatelessWidget {
 | 
			
		||||
| 
						 | 
				
			
			@ -15,19 +21,44 @@ class HyM extends StatelessWidget {
 | 
			
		|||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
    final GoRouter _router = GoRouter(
 | 
			
		||||
        initialLocation: '/',
 | 
			
		||||
        routes: [
 | 
			
		||||
          GoRoute(
 | 
			
		||||
            path: "/",
 | 
			
		||||
            builder: (context, state) => SplashScreen(),
 | 
			
		||||
          ),
 | 
			
		||||
          GoRoute(
 | 
			
		||||
            path: "/login",
 | 
			
		||||
            builder: (context, state) => const LoginPage(),
 | 
			
		||||
          ),
 | 
			
		||||
          GoRoute(
 | 
			
		||||
            path: "/home",
 | 
			
		||||
            builder: (context, state) => HomeScreen(),
 | 
			
		||||
          ),
 | 
			
		||||
          GoRoute(
 | 
			
		||||
            path: "/contacts",
 | 
			
		||||
            builder: (context, state) => ContactsPage(),
 | 
			
		||||
          ),
 | 
			
		||||
          GoRoute(
 | 
			
		||||
              path: "/email/:subject/:target/:viewspecs/:emailID",
 | 
			
		||||
              builder: (context, state) {
 | 
			
		||||
                final subject = state.pathParameters['subject']!;
 | 
			
		||||
                final target = state.pathParameters['target']!;
 | 
			
		||||
                final viewspecs = state.pathParameters['viewspecs']!;
 | 
			
		||||
                final emailId = state.pathParameters['emailID']!;
 | 
			
		||||
                return Routinghandler.fromParameters(
 | 
			
		||||
                    "main anchor", subject, target, viewspecs, emailId);
 | 
			
		||||
              }),
 | 
			
		||||
        ]);
 | 
			
		||||
    return MaterialApp.router(
 | 
			
		||||
      debugShowCheckedModeBanner: false,
 | 
			
		||||
      theme: ThemeData.light(),
 | 
			
		||||
      theme: ThemeData(
 | 
			
		||||
        colorScheme: ColorScheme.light(),
 | 
			
		||||
        useMaterial3: true,
 | 
			
		||||
      ),
 | 
			
		||||
      title: 'HyM',
 | 
			
		||||
      // home: HomeScreen(),
 | 
			
		||||
      initialRoute: "/",
 | 
			
		||||
 | 
			
		||||
      routes: {
 | 
			
		||||
        "/": (context) => SplashScreen(),
 | 
			
		||||
        "/login": (context) => const LoginPage(),
 | 
			
		||||
        "/home": (context) => HomeScreen(),
 | 
			
		||||
        "/contacts": (context) => ContactsPage(),
 | 
			
		||||
      },
 | 
			
		||||
      routerConfig: _router,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										229
									
								
								lib/routingHandler.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,229 @@
 | 
			
		|||
import 'package:crab_ui/collapsableEmails.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:markdown/markdown.dart' as md;
 | 
			
		||||
import 'package:markdown_widget/markdown_widget.dart';
 | 
			
		||||
import 'api_service.dart';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
 | 
			
		||||
class Routinghandler extends StatefulWidget {
 | 
			
		||||
  Routinghandler(String link) {
 | 
			
		||||
    bool anchorDone = false;
 | 
			
		||||
 | 
			
		||||
    bool docNameDone = false;
 | 
			
		||||
 | 
			
		||||
    bool viewspecsDone = false;
 | 
			
		||||
 | 
			
		||||
    bool targetDone = false;
 | 
			
		||||
 | 
			
		||||
    bool emailIdDone = false;
 | 
			
		||||
 | 
			
		||||
    for (int letter = 0; letter < link.length; letter++) {
 | 
			
		||||
      if (!anchorDone) {
 | 
			
		||||
        if (link[letter] != '<') {
 | 
			
		||||
          //when the anchor hasnt been dissected
 | 
			
		||||
          anchor += link[letter];
 | 
			
		||||
        } else {
 | 
			
		||||
          anchorDone = true;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!docNameDone) {
 | 
			
		||||
        if (link[letter] != ',') {
 | 
			
		||||
          //when the docName hasnt been dissected
 | 
			
		||||
          docName += link[letter];
 | 
			
		||||
        } else {
 | 
			
		||||
          docNameDone = true;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!targetDone) {
 | 
			
		||||
        if (link[letter] != ':') {
 | 
			
		||||
          target += link[letter];
 | 
			
		||||
        } else {
 | 
			
		||||
          targetDone = true;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!viewspecsDone) {
 | 
			
		||||
        if (link[letter] != '>') {
 | 
			
		||||
          //when the docName hasnt been dissected
 | 
			
		||||
          viewspecs += link[letter];
 | 
			
		||||
        } else {
 | 
			
		||||
          viewspecsDone = true;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!emailIdDone) {
 | 
			
		||||
        emailID += link[letter];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    anchor = anchor.trim();
 | 
			
		||||
    docName = docName.trim();
 | 
			
		||||
    target = target.trim();
 | 
			
		||||
    viewspecs = viewspecs.trim();
 | 
			
		||||
    print("inside constructor uwu $emailID");
 | 
			
		||||
    emailID = emailID.trim();
 | 
			
		||||
  }
 | 
			
		||||
  Routinghandler.fromParameters(String anchor, String docName, String target,
 | 
			
		||||
      String viewspecs, String emailID) {
 | 
			
		||||
        this.anchor = anchor;
 | 
			
		||||
        this.docName = docName;
 | 
			
		||||
        this.viewspecs = viewspecs;
 | 
			
		||||
        this.target = target;
 | 
			
		||||
        this.emailID = emailID;
 | 
			
		||||
  }
 | 
			
		||||
  Routinghandler.copyConstructor(Routinghandler other) {
 | 
			
		||||
    anchor = other.anchor;
 | 
			
		||||
    docName = other.docName;
 | 
			
		||||
    viewspecs = other.viewspecs;
 | 
			
		||||
    target = other.target;
 | 
			
		||||
    emailID = other.emailID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String anchor = '';
 | 
			
		||||
  String docName = '';
 | 
			
		||||
  String viewspecs = '';
 | 
			
		||||
  String target = '';
 | 
			
		||||
  String emailID = '';
 | 
			
		||||
 | 
			
		||||
  void goToLink() {
 | 
			
		||||
    // bool anchorDone = false;
 | 
			
		||||
 | 
			
		||||
    // bool docNameDone = false;
 | 
			
		||||
 | 
			
		||||
    // bool viewspecsDone = false;
 | 
			
		||||
 | 
			
		||||
    // bool targetDone = false;
 | 
			
		||||
 | 
			
		||||
    // for (int letter = 0; letter < link.length; letter++) {
 | 
			
		||||
    //   if (!anchorDone) {
 | 
			
		||||
    //     if (link[letter] != '<') {
 | 
			
		||||
    //       //when the anchor hasnt been dissected
 | 
			
		||||
    //       anchor += link[letter];
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //       anchorDone = true;
 | 
			
		||||
    //     }
 | 
			
		||||
    //   } else if (!docNameDone) {
 | 
			
		||||
    //     if (link[letter] != ',') {
 | 
			
		||||
    //       //when the docName hasnt been dissected
 | 
			
		||||
    //       docName += link[letter];
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //       docNameDone = true;
 | 
			
		||||
    //     }
 | 
			
		||||
    //   } else if (!targetDone) {
 | 
			
		||||
    //     if (link[letter] != ':') {
 | 
			
		||||
    //       target += link[letter];
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //       targetDone = true;
 | 
			
		||||
    //     }
 | 
			
		||||
    //   } else if (!viewspecsDone) {
 | 
			
		||||
    //     if (link[letter] != '>') {
 | 
			
		||||
    //       //when the docName hasnt been dissected
 | 
			
		||||
    //       viewspecs += link[letter];
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //       viewspecsDone = true;
 | 
			
		||||
    //     }
 | 
			
		||||
    //   }
 | 
			
		||||
    // }
 | 
			
		||||
    print("anchor $anchor");
 | 
			
		||||
    print("docName $docName");
 | 
			
		||||
    print("target $target");
 | 
			
		||||
    print("viewspecs $viewspecs");
 | 
			
		||||
    print("emailID $emailID");
 | 
			
		||||
    //now it should open a widget in that part
 | 
			
		||||
    //maybe i need a rewrite
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String getEmailID() {
 | 
			
		||||
    return emailID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _RoutingHandlerState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _RoutingHandlerState extends State<Routinghandler> {
 | 
			
		||||
  List<String> markdownContent = [];
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
  AugmentTree? aug;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    // TODO: implement initState
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _loadMarkdown();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _loadMarkdown() async {
 | 
			
		||||
    String folder = ApiService.currFolder;
 | 
			
		||||
    print(widget.getEmailID());
 | 
			
		||||
    String emailID = widget.emailID;
 | 
			
		||||
    print("inside _loadMarkdown in routinghandler $emailID");
 | 
			
		||||
    markdownContent =
 | 
			
		||||
        await ApiService().fetchMarkdownContent([emailID], folder);
 | 
			
		||||
    // print(markdownContent);
 | 
			
		||||
    aug = AugmentTree.fromMD(markdownContent[0]);
 | 
			
		||||
    aug!.addNumbering();
 | 
			
		||||
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (!_isLoaded) {
 | 
			
		||||
      return const Center(
 | 
			
		||||
        child: CircularProgressIndicator(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text("Routing Handler"),
 | 
			
		||||
          leading: IconButton(
 | 
			
		||||
              onPressed: () {
 | 
			
		||||
                GoRouter.of(context).go('/home');
 | 
			
		||||
              },
 | 
			
		||||
              icon: const Icon(Icons.arrow_back_ios)),
 | 
			
		||||
        ),
 | 
			
		||||
        body: ConstrainedBox(
 | 
			
		||||
            constraints: BoxConstraints(
 | 
			
		||||
              minHeight: 100,
 | 
			
		||||
              maxHeight: MediaQuery.of(context).size.height * 0.7,
 | 
			
		||||
            ),
 | 
			
		||||
            child: SingleChildScrollView(
 | 
			
		||||
                //inside here put the bunch rows
 | 
			
		||||
                //make rows of markdownBlocks, but firstly i need to conveert the content into a tree
 | 
			
		||||
                // child:MarkdownBlock(data: markdownContent[0])
 | 
			
		||||
 | 
			
		||||
                child: Column(children: [
 | 
			
		||||
              for (int i = 0; i < this.aug!.children![0]!.children.length; i++)
 | 
			
		||||
                Row(
 | 
			
		||||
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      // if (leftNumbering)
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          aug!.children![0]!.children![i]!.numbering,
 | 
			
		||||
                          style: TextStyle(
 | 
			
		||||
                              color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                          child: Align(
 | 
			
		||||
                              alignment: Alignment.topLeft,
 | 
			
		||||
                              child: Wrap(
 | 
			
		||||
                                children: [
 | 
			
		||||
                                  MarkdownBlock(
 | 
			
		||||
                                      data: aug!.children![0]!.children![i]!
 | 
			
		||||
                                              .data ??
 | 
			
		||||
                                          ''),
 | 
			
		||||
                                ],
 | 
			
		||||
                              ))),
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          aug!.children![0]!.children![i]!.numbering,
 | 
			
		||||
                          style: TextStyle(
 | 
			
		||||
                              color: Color(Colors.purple[400]!.value)),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ]),
 | 
			
		||||
            ]))));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,145 +1,3 @@
 | 
			
		|||
import 'package:crab_ui/augment.dart';
 | 
			
		||||
import 'package:web/web.dart' as web;
 | 
			
		||||
import 'dart:ui_web' as ui;
 | 
			
		||||
import 'dart:js_interop';
 | 
			
		||||
import 'structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class SonicEmailView extends StatefulWidget {
 | 
			
		||||
  SerializableMessage email;
 | 
			
		||||
  String emailHTML;
 | 
			
		||||
 | 
			
		||||
  SonicEmailView({required this.email, required this.emailHTML});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _SonicEmailViewState createState() => _SonicEmailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SonicEmailViewState extends State<SonicEmailView> {
 | 
			
		||||
  String viewTypeIDs = "";
 | 
			
		||||
  int heightOFViewtype = 0;
 | 
			
		||||
  bool _isLoaded = false;
 | 
			
		||||
 | 
			
		||||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    AugmentClasses.handleJump(spanId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _init() async {
 | 
			
		||||
    await _registerViewFactory(widget.emailHTML);
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _registerViewFactory(String currentContent) async {
 | 
			
		||||
    // setState(() { //update to do item per item
 | 
			
		||||
    // each item to have itsviewtype ID
 | 
			
		||||
    // is this necessarey here??
 | 
			
		||||
 | 
			
		||||
    //could just move to collapsable
 | 
			
		||||
 | 
			
		||||
    // for (var emailHTML in widget.threadHTML) {
 | 
			
		||||
    String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
 | 
			
		||||
 | 
			
		||||
    final ghost = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
      ..style.visibility = 'hidden'
 | 
			
		||||
      ..style.position = 'absolute'
 | 
			
		||||
      ..style.width = '100%'
 | 
			
		||||
      ..style.overflow = 'auto'
 | 
			
		||||
      ..innerHTML = currentContent.toJS;
 | 
			
		||||
    web.document.body?.append(ghost);
 | 
			
		||||
    await Future.delayed(Duration(milliseconds: 10));
 | 
			
		||||
 | 
			
		||||
    final heightOfEmail = ghost.scrollHeight;
 | 
			
		||||
    ghost.remove();
 | 
			
		||||
 | 
			
		||||
    final HTMLsnippet = web.document.createElement('div') as web.HTMLDivElement
 | 
			
		||||
      ..id = viewTypeId
 | 
			
		||||
      ..innerHTML = widget
 | 
			
		||||
          .emailHTML.toJS; // temporarily index because it has to do all of them
 | 
			
		||||
    HTMLsnippet.style
 | 
			
		||||
      ..width = '100%'
 | 
			
		||||
      ..height = '${heightOfEmail}px'
 | 
			
		||||
      ..overflow = 'auto'
 | 
			
		||||
      ..scrollBehavior = 'smooth';
 | 
			
		||||
 | 
			
		||||
    ui.platformViewRegistry.registerViewFactory(
 | 
			
		||||
      viewTypeId,
 | 
			
		||||
      (int viewId) => HTMLsnippet,
 | 
			
		||||
    );
 | 
			
		||||
    this.viewTypeIDs = viewTypeId;
 | 
			
		||||
    this.heightOFViewtype = heightOfEmail;
 | 
			
		||||
    print(viewTypeIDs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return _isLoaded
 | 
			
		||||
        ? Scaffold(
 | 
			
		||||
            appBar: AppBar(title: Text(widget.email.subject)),
 | 
			
		||||
            body: Stack(
 | 
			
		||||
              children: [
 | 
			
		||||
                Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    EmailToolbar(
 | 
			
		||||
                        onButtonPressed: () => {},
 | 
			
		||||
                        onJumpToSpan: _scrollToNumber),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      // title of email
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          widget.email.subject,
 | 
			
		||||
                          style: TextStyle(fontSize: 30),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          'from ${widget.email.name}',
 | 
			
		||||
                          style: TextStyle(fontSize: 18),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Text(
 | 
			
		||||
                          '<${widget.email.from}>',
 | 
			
		||||
                          style: TextStyle(fontSize: 18),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Spacer(),
 | 
			
		||||
                        Text(
 | 
			
		||||
                          '${widget.email.date}',
 | 
			
		||||
                          textAlign: TextAlign.right,
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    // TODO: make a case where if one of these is the user's email it just says me :)))))
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(
 | 
			
		||||
                          'to ${widget.email.to.toString()}',
 | 
			
		||||
                          style: TextStyle(fontSize: 15),
 | 
			
		||||
                        )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    Expanded(
 | 
			
		||||
                        // child: SizedBox(
 | 
			
		||||
                        // height: heightOFViewtype.toDouble(),
 | 
			
		||||
                        child: HtmlElementView(
 | 
			
		||||
                      key: UniqueKey(), viewType: this.viewTypeIDs,
 | 
			
		||||
                      // ),
 | 
			
		||||
                    ))
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
        : const Center(
 | 
			
		||||
            child: CircularProgressIndicator(),
 | 
			
		||||
          );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export 'SonicEmailViewStub.dart'
 | 
			
		||||
  if (dart.library.js_interop) 'SonicEmailViewWeb.dart'
 | 
			
		||||
  if (dart.library.io) 'SonicEmailViewAndroid.dart';
 | 
			
		||||
							
								
								
									
										180
									
								
								lib/structs.dart
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
//data structures
 | 
			
		||||
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
import 'package:markdown/markdown.dart' as md;
 | 
			
		||||
 | 
			
		||||
class GetThreadResponse {
 | 
			
		||||
  final int id;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +11,7 @@ class GetThreadResponse {
 | 
			
		|||
  final String from_name;
 | 
			
		||||
  final String from_address;
 | 
			
		||||
  final List<MailAddress> to;
 | 
			
		||||
  late bool seen;
 | 
			
		||||
 | 
			
		||||
  GetThreadResponse({
 | 
			
		||||
    required this.id,
 | 
			
		||||
| 
						 | 
				
			
			@ -19,19 +21,20 @@ class GetThreadResponse {
 | 
			
		|||
    required this.from_name,
 | 
			
		||||
    required this.from_address,
 | 
			
		||||
    required this.to,
 | 
			
		||||
    required this.seen,
 | 
			
		||||
  });
 | 
			
		||||
  factory GetThreadResponse.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    var toList = json['to'] as List<dynamic>;
 | 
			
		||||
 | 
			
		||||
    return GetThreadResponse(
 | 
			
		||||
      id: json['id'],
 | 
			
		||||
      messages: List<String>.from(json['messages']),
 | 
			
		||||
      subject: json['subject'],
 | 
			
		||||
      date: DateTime.parse(json['date']),
 | 
			
		||||
      from_name: json['from_name'],
 | 
			
		||||
      from_address: json['from_address'],
 | 
			
		||||
      to: toList.map((i) => MailAddress.fromJson(i)).toList(),
 | 
			
		||||
    );
 | 
			
		||||
        id: json['id'],
 | 
			
		||||
        messages: List<String>.from(json['messages']),
 | 
			
		||||
        subject: json['subject'],
 | 
			
		||||
        date: DateTime.parse(json['date']),
 | 
			
		||||
        from_name: json['from_name'],
 | 
			
		||||
        from_address: json['from_address'],
 | 
			
		||||
        to: toList.map((i) => MailAddress.fromJson(i)).toList(),
 | 
			
		||||
        seen: json['seen']);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +81,7 @@ class SerializableMessage {
 | 
			
		|||
    required this.subject,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    required this.uid,
 | 
			
		||||
    required this.list, //email list???
 | 
			
		||||
    required this.list, //folder
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.in_reply_to,
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +130,8 @@ class AttachmentInfoList extends Iterable<AttachmentInfo> {
 | 
			
		|||
  AttachmentInfoList(this._attachments);
 | 
			
		||||
 | 
			
		||||
  factory AttachmentInfoList.fromJsonList(List<Map<String, dynamic>> jsonList) {
 | 
			
		||||
    return AttachmentInfoList(jsonList.map((json) => AttachmentInfo.fromJson(json)).toList());
 | 
			
		||||
    return AttachmentInfoList(
 | 
			
		||||
        jsonList.map((json) => AttachmentInfo.fromJson(json)).toList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +147,160 @@ class AttachmentResponse {
 | 
			
		|||
  AttachmentResponse({required this.name, required this.data});
 | 
			
		||||
 | 
			
		||||
  factory AttachmentResponse.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    return AttachmentResponse(name: json["name"], data: Uint8List.fromList(List<int>.from(json["data"])));
 | 
			
		||||
    return AttachmentResponse(
 | 
			
		||||
        name: json["name"],
 | 
			
		||||
        data: Uint8List.fromList(List<int>.from(json["data"])));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AugmentTree {
 | 
			
		||||
  List<AugmentTree> children = [];
 | 
			
		||||
 | 
			
		||||
  String data = '';
 | 
			
		||||
  AugmentTree? parent;
 | 
			
		||||
  String ogTag = '';
 | 
			
		||||
  String numbering = '';
 | 
			
		||||
  Map<String, int> hirarchyDict = {
 | 
			
		||||
    "h1": 1,
 | 
			
		||||
    "h2": 2,
 | 
			
		||||
    "h3": 3,
 | 
			
		||||
    "h4": 4,
 | 
			
		||||
    "h5": 5,
 | 
			
		||||
    "h6": 6,
 | 
			
		||||
    "p": 8,
 | 
			
		||||
    "ul": 8,
 | 
			
		||||
    "li": 8,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  AugmentTree();
 | 
			
		||||
 | 
			
		||||
  AugmentTree.fromMD(String rawMD) {
 | 
			
		||||
    //makes raw MD into an augmentTree
 | 
			
		||||
    print("started markdown2tree");
 | 
			
		||||
    final List<md.Node> nakedList = md.Document().parseLines(rawMD.split(
 | 
			
		||||
        '\n')); //emails md is the index of the email in the thread, since this only handles one thus it shall be removed
 | 
			
		||||
    // AugmentTree zoomTreeRoot = AugmentTree();
 | 
			
		||||
    for (var node in nakedList) {
 | 
			
		||||
      //maybe do an add function, but isn't this it?
 | 
			
		||||
      if (node is md.Element) {
 | 
			
		||||
        AugmentTree temp = AugmentTree();
 | 
			
		||||
        temp.data = node.textContent;
 | 
			
		||||
        temp.ogTag = node.tag;
 | 
			
		||||
        if (hirarchyDict.containsKey(node.tag)) {
 | 
			
		||||
          // make this O(1)
 | 
			
		||||
          _add2Tree(this, node);
 | 
			
		||||
          // print(node);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.addNumbering();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _add2Tree(AugmentTree tree, md.Element node2add) {
 | 
			
		||||
    // adds node to its corresponding place
 | 
			
		||||
    AugmentTree newNode = AugmentTree();
 | 
			
		||||
    newNode.setData(node2add.textContent);
 | 
			
		||||
    newNode.ogTag = node2add.tag;
 | 
			
		||||
    // cases,
 | 
			
		||||
    //1. a node that comes is lower than the root.children last, if so it goes beneath it
 | 
			
		||||
    if (tree.children.isEmpty) {
 | 
			
		||||
      // new level to be created when totally empty
 | 
			
		||||
      tree.children.add(newNode);
 | 
			
		||||
      newNode.parent = tree;
 | 
			
		||||
    } else if (tree.children.isNotEmpty &&
 | 
			
		||||
        tree.children.last.ogTag.isNotEmpty) {
 | 
			
		||||
      if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) < // e.g. new node is h1 and old is h2, heapify
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        //have to figure out the borthers
 | 
			
		||||
        //assuming it all goes right
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          return;
 | 
			
		||||
        } else if (tree.children.last.parent == null) {
 | 
			
		||||
          // becomes the new top level
 | 
			
		||||
          for (AugmentTree brother in tree.children) {
 | 
			
		||||
            brother.parent = newNode;
 | 
			
		||||
          }
 | 
			
		||||
          tree.children = [newNode];
 | 
			
		||||
        } else {
 | 
			
		||||
          newNode.parent = tree;
 | 
			
		||||
          tree.children.add(newNode);
 | 
			
		||||
        }
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ??
 | 
			
		||||
              -1) > // go down e.g. new node is h3 and old is h2 or something
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        if ((hirarchyDict[node2add.tag] ?? -1) == -1 ||
 | 
			
		||||
            (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) {
 | 
			
		||||
          print(
 | 
			
		||||
              'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}');
 | 
			
		||||
          print("-1 ${tree.children.last.ogTag}");
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _add2Tree(tree.children.last, node2add);
 | 
			
		||||
      } else if ((hirarchyDict[node2add.tag] ?? -1) ==
 | 
			
		||||
          (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
 | 
			
		||||
        tree.children.add(newNode);
 | 
			
		||||
        newNode.parent = tree;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setData(String data) {
 | 
			
		||||
    this.data = data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static String _intToLetter(int index) {
 | 
			
		||||
    return String.fromCharCode('a'.runes.first + index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void addNumbering({String prefix = ''}) {
 | 
			
		||||
    //if called in root, numbers them all
 | 
			
		||||
    for (int i = 0; i < children.length; i++) {
 | 
			
		||||
      final child = children[i];
 | 
			
		||||
      String childNumbering;
 | 
			
		||||
      bool parentIsLettered = prefix.contains(RegExp(r'[a-z]'));
 | 
			
		||||
      if (prefix.isEmpty) {
 | 
			
		||||
        parentIsLettered = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        parentIsLettered = prefix.runes.last >= 'a'.runes.first &&
 | 
			
		||||
            prefix.runes.last <= 'z'.runes.first;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (prefix.isEmpty) {
 | 
			
		||||
        // Top-level children (direct children of the original root being numbered) get 1, 2, 3...
 | 
			
		||||
        childNumbering = (i + 1).toString();
 | 
			
		||||
      } else if (parentIsLettered) {
 | 
			
		||||
        // Deeper children get '1a', '1b', '2a', '2b', etc.
 | 
			
		||||
        childNumbering = '$prefix${i + 1}';
 | 
			
		||||
      } else {
 | 
			
		||||
        childNumbering = '$prefix${_intToLetter(i)}';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      child.numbering = childNumbering;
 | 
			
		||||
 | 
			
		||||
      // Recursively call for children
 | 
			
		||||
      child.addNumbering(prefix: childNumbering);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//perhaps make a struct that builds augment tree, since its so complex and needs to be like recursive
 | 
			
		||||
 | 
			
		||||
class MarkdownParsed {
 | 
			
		||||
  //struct for holding the MD given in endpoint //not used
 | 
			
		||||
  final String text;
 | 
			
		||||
  MarkdownParsed({required this.text});
 | 
			
		||||
  factory MarkdownParsed.fromJson(Map<String, String> json) {
 | 
			
		||||
    return MarkdownParsed(
 | 
			
		||||
      text: json['md'] ?? '',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//should make an md to tree class/struct
 | 
			
		||||
 | 
			
		||||
// make a for loop of rows with markdown
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										522
									
								
								pubspec.lock
									
										
									
									
									
								
							
							
						
						
							
								
								
									
										13
									
								
								pubspec.yaml
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -12,14 +12,12 @@ dependencies:
 | 
			
		|||
  flutter:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
  http: 1.2.2
 | 
			
		||||
  flutter_html_all: 3.0.0-beta.2
 | 
			
		||||
  flutter_widget_from_html: ^0.10.0
 | 
			
		||||
  shared_preferences: ^2.0.6
 | 
			
		||||
  encrypt: ^5.0.0
 | 
			
		||||
  pointycastle: ^3.4.0
 | 
			
		||||
  mime: ^1.0.3
 | 
			
		||||
  pointer_interceptor: ^0.10.1+2
 | 
			
		||||
  file_saver: ^0.2.14
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  english_words: ^4.0.0
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +26,14 @@ dependencies:
 | 
			
		|||
  pdfrx: ^1.0.94
 | 
			
		||||
  photo_view: ^0.15.0
 | 
			
		||||
  web: ^1.1.1
 | 
			
		||||
  flutter_widget_from_html: ^0.16.0
 | 
			
		||||
  html2md: ^1.3.2
 | 
			
		||||
  markdown_widget: ^2.3.2+8
 | 
			
		||||
  markdown: ^7.3.0
 | 
			
		||||
  go_router: ^16.0.0
 | 
			
		||||
  super_editor: ^0.3.0-dev.27
 | 
			
		||||
  super_editor_markdown: 0.1.8
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +45,7 @@ dependency_overrides:
 | 
			
		|||
  flutter_layout_grid: 2.0.7
 | 
			
		||||
  flutter_math_fork: 0.7.2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
flutter:
 | 
			
		||||
  uses-material-design: true
 | 
			
		||||
  assets:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||