WIP: 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
|
44
android/app/build.gradle.kts
Normal file
@ -0,0 +1,44 @@
|
||||
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
|
||||
}
|
||||
|
||||
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.0 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.0 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"
|
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")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
150
lib/SonicEmailViewWeb.dart
Normal file
@ -0,0 +1,150 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@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
|
||||
),
|
||||
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,22 +1,12 @@
|
||||
// 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 = "";
|
||||
@ -148,7 +138,6 @@ class ApiService {
|
||||
} catch (e) {
|
||||
print('_getEmailContent caught error: $e');
|
||||
}
|
||||
// return content;
|
||||
return HTMLofThread;
|
||||
}
|
||||
|
||||
@ -351,308 +340,41 @@ 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');
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// TODO: void _invisibility(String ) //to make purple numbers not visible
|
||||
|
||||
@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?");
|
||||
|
||||
// _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"
|
||||
|
||||
//
|
||||
),
|
||||
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,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
|
||||
// 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(),
|
||||
// );
|
||||
// }
|
||||
|
||||
// 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),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
));
|
||||
return MDofThread;
|
||||
}
|
||||
}
|
||||
|
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),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]));
|
||||
}
|
||||
}
|
@ -1,23 +1,17 @@
|
||||
// import 'dart:ffi';
|
||||
|
||||
import 'package:crab_ui/api_service.dart';
|
||||
import 'package:crab_ui/attachmentDownload.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:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'attachmentWidget.dart';
|
||||
|
||||
class EmailToolbar extends StatefulWidget {
|
||||
final Function(String) onJumpToSpan;
|
||||
final Function(String) onJumpToNumbering;
|
||||
final Function(String) onViewspecs;
|
||||
final VoidCallback onButtonPressed;
|
||||
|
||||
EmailToolbar(
|
||||
{Key? key, required this.onButtonPressed, required this.onJumpToSpan})
|
||||
{Key? key, required this.onButtonPressed, required this.onJumpToNumbering, required this.onViewspecs})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -26,7 +20,8 @@ class EmailToolbar extends StatefulWidget {
|
||||
|
||||
class _DynamicClassesAugment extends State<EmailToolbar> {
|
||||
String selectedClass = 'Class 1';
|
||||
// TextEditingController _jumpController = TextEditingController();
|
||||
TextEditingController _jumpController = TextEditingController();
|
||||
TextEditingController _viewspecsController = TextEditingController();
|
||||
|
||||
// late final FocusNode _JumpItemfocusNode;
|
||||
// late final FocusNode _viewSpecsfocusNode;
|
||||
@ -53,7 +48,7 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
|
||||
void dispose() {
|
||||
// _JumpItemfocusNode.dispose();
|
||||
// _viewSpecsfocusNode.dispose();
|
||||
// _jumpController.dispose();
|
||||
_jumpController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -78,20 +73,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 +133,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 +144,7 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
|
||||
onSubmitted: (value) {
|
||||
print("onSubmitted");
|
||||
if (value.isNotEmpty) {
|
||||
widget.onJumpToSpan(value);
|
||||
widget.onJumpToNumbering(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -186,14 +181,18 @@ 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(
|
||||
@ -491,30 +490,8 @@ class AugmentClasses {
|
||||
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) {}
|
||||
|
@ -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';
|
435
lib/collapsableEmailsAndroid.dart
Normal file
@ -0,0 +1,435 @@
|
||||
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;
|
||||
|
||||
CollapsableEmails(
|
||||
{required this.thread,
|
||||
required this.threadMarkdown,
|
||||
required this.threadIDs,
|
||||
this.targetJumpNumbering,
|
||||
this.targetViewspecs,
|
||||
});
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
23
lib/collapsableEmailsStub.dart
Normal file
@ -0,0 +1,23 @@
|
||||
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});
|
||||
|
||||
@override
|
||||
State<CollapsableEmails> createState() => _CollapsableEmailsState();
|
||||
}
|
||||
|
||||
class _CollapsableEmailsState extends State<CollapsableEmails> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(body: Text("collapsable stud"));
|
||||
}
|
||||
}
|
450
lib/collapsableEmailsWeb.dart
Normal file
@ -0,0 +1,450 @@
|
||||
import 'package:english_words/english_words.dart';
|
||||
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; to be replaced with the MD
|
||||
final List<String> threadMarkdown;
|
||||
final String threadIDs;
|
||||
final String? targetJumpNumbering;
|
||||
final String? targetViewspecs;
|
||||
|
||||
const CollapsableEmails({
|
||||
required this.thread,
|
||||
// required this.threadHTML,
|
||||
required this.threadMarkdown,
|
||||
required this.threadIDs,
|
||||
this.targetJumpNumbering,
|
||||
this.targetViewspecs,
|
||||
});
|
||||
|
||||
@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": 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 = [];
|
||||
bool zoomOut = false;
|
||||
bool zoomIn = true;
|
||||
late List<AugmentTree> threadNodes = [];
|
||||
static bool leftNumbering = true;
|
||||
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();
|
||||
}
|
||||
|
||||
// void _markdownConverter() async {
|
||||
// // to list of markdown
|
||||
// // for (int email = 0; email < widget.threadHTML.length; email++) {
|
||||
// // String markdown = html2md.convert(widget.threadHTML[email]);
|
||||
// // allMarkdown.add(markdown);
|
||||
// // }
|
||||
// for (int email = 0; email < widget.threadMarkdown.length; email++) {
|
||||
// allMarkdown.add(email);
|
||||
// }
|
||||
// }
|
||||
|
||||
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), //show the tree
|
||||
// child: _buildForZooms(key: ValueKey(currentZoomNode)),
|
||||
),
|
||||
Divider(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
])
|
||||
: const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
// import 'package:http/http.dart' as http;
|
||||
// import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class ContactsPage extends StatefulWidget {
|
||||
const ContactsPage({super.key});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'api_service.dart';
|
||||
import 'structs.dart';
|
||||
import 'emailView.dart';
|
||||
|
||||
class EmailListScreen extends StatelessWidget {
|
||||
final List<GetThreadResponse> emails;
|
||||
@ -140,7 +140,8 @@ class EmailPageState extends State<EmailPage> {
|
||||
return Scaffold(
|
||||
body: EmailListScreen(
|
||||
emails: emails,
|
||||
getEmailContent: apiService.fetchEmailContent,
|
||||
// getEmailContent: apiService.fetchEmailContent,
|
||||
getEmailContent: apiService.fetchMarkdownContent,
|
||||
folder: widget.selectedFolder, //try to grab from it directly
|
||||
),
|
||||
);
|
||||
|
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';
|
115
lib/emailViewAndroid.dart
Normal file
@ -0,0 +1,115 @@
|
||||
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){
|
||||
|
||||
}
|
||||
|
||||
@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,
|
||||
|
||||
),
|
||||
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")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
143
lib/emailViewWeb.dart
Normal file
@ -0,0 +1,143 @@
|
||||
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 '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???
|
||||
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},
|
||||
];
|
||||
String? _targetJumpNumbering;
|
||||
String? _targetViewspecs;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
void _scrollToNumber(String spanId) {
|
||||
AugmentClasses.handleJump(spanId);
|
||||
}
|
||||
|
||||
void _handleJumpRequest(String numbering) {
|
||||
setState(() {
|
||||
_targetJumpNumbering = numbering;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleViewspecsRequest(String viewspecsCommand) {
|
||||
setState(() {
|
||||
_targetViewspecs = viewspecsCommand;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: void _invisibility(String ) //to make purple numbers not visible
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ApiService.currThreadID = widget.id;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.name),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
EmailToolbar(
|
||||
onJumpToNumbering: _handleJumpRequest,
|
||||
onViewspecs: _handleViewspecsRequest,
|
||||
onButtonPressed: () => {print("email tool bar pressed")},
|
||||
),
|
||||
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, // old html
|
||||
threadMarkdown: widget.emailContent,
|
||||
threadIDs: widget.id,
|
||||
targetJumpNumbering: _targetJumpNumbering,
|
||||
targetViewspecs: _targetViewspecs,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import 'package:crab_ui/sonicEmailView.dart';
|
||||
|
||||
import 'folder_drawer.dart';
|
||||
import 'structs.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
// import 'package:flutter/widgets.dart';
|
||||
import 'api_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'email.dart';
|
||||
@ -148,16 +148,6 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
builder: (context) =>SonicEmailView(
|
||||
email: email,
|
||||
emailHTML: emailContent[0])
|
||||
// builder: (context) => EmailView(
|
||||
// emailContent: emailContent,
|
||||
// from: email.from,
|
||||
// name: email.name,
|
||||
// to: email.to.toString(),
|
||||
// subject: email.subject,
|
||||
// date: email.date.toString(),
|
||||
// id: email.id.toString(),
|
||||
// messages: [email.id],
|
||||
// ),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -165,28 +155,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
},
|
||||
separatorBuilder: (context, index) => Divider(),
|
||||
),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text("Results for: $query", style: TextStyle(fontSize: 24)),
|
||||
// // Display the actual data
|
||||
// Text(result[0].name), // Accessing the first result safely
|
||||
// Text(result[0].from), // Displaying the 'from' field as an example
|
||||
// Text(result[0].hash),
|
||||
// Text(result[0].subject),
|
||||
// Text(result[0].uid.toString()),
|
||||
// Text(result[0].list),
|
||||
// Text(result[0].id),
|
||||
|
||||
// // Add more fields or customize the display
|
||||
// // SerializableEmailListScreen(emails: result, getEmailContent: getEmailContent)
|
||||
// // Expanded(
|
||||
|
||||
// // child:
|
||||
// // ),
|
||||
// ],
|
||||
);
|
||||
// );
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -201,6 +171,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 20, 0 , 20),
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: FolderDrawer(
|
||||
apiService: apiService,
|
||||
@ -208,7 +182,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
_emailPageKey.currentState?.updateSelectedFolder(folder);
|
||||
},
|
||||
),
|
||||
body: Stack(
|
||||
body: Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
@ -266,8 +244,12 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 800,
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: 800,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
@ -295,11 +277,12 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 16,
|
||||
width: 8,
|
||||
),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 40,
|
||||
child: ElevatedButton(
|
||||
onPressed: _showOptionsSearchDialog,
|
||||
@ -431,35 +414,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// void _showPopupMenu(BuildContext context, Offset position) async {
|
||||
// final RenderBox overlay =
|
||||
// Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||
|
||||
// await showMenu<String>(
|
||||
// context: context,
|
||||
// position: RelativeRect.fromLTRB(
|
||||
// position.dx,
|
||||
// position.dy,
|
||||
// overlay.size.width - position.dx,
|
||||
// overlay.size.height - position.dy,
|
||||
// ),
|
||||
// items: <PopupMenuEntry<String>>[
|
||||
// PopupMenuItem<String>(
|
||||
// value: 'Open',
|
||||
// child: Text('Open'),
|
||||
// ),
|
||||
// PopupMenuItem<String>(
|
||||
// value: 'Reply',
|
||||
// child: Text('Reply'),
|
||||
// ),
|
||||
// PopupMenuItem<String>(
|
||||
// value: 'Delete',
|
||||
// child: Text('Delete'),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
@ -17,7 +17,10 @@ class HyM extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.light(),
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.light(),
|
||||
useMaterial3: true,
|
||||
),
|
||||
title: 'HyM',
|
||||
// home: HomeScreen(),
|
||||
initialRoute: "/",
|
||||
|
@ -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';
|
@ -127,7 +127,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 +144,67 @@ 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 = '';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MarkdownParsed{
|
||||
final String text;
|
||||
MarkdownParsed({required this.text});
|
||||
factory MarkdownParsed.fromJson(Map<String, String> json){
|
||||
return MarkdownParsed(
|
||||
text: json['md'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
522
pubspec.lock
@ -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,10 @@ 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
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -39,6 +41,7 @@ dependency_overrides:
|
||||
flutter_layout_grid: 2.0.7
|
||||
flutter_math_fork: 0.7.2
|
||||
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
|