From 9ecf126869dc17ed7d022220a96c9413b0c971b0 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 22 May 2025 23:19:44 -0400 Subject: [PATCH 01/64] android build --- .metadata | 30 +- android/.gitignore | 14 + android/app/build.gradle.kts | 44 ++ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 45 ++ .../com/example/crab_ui/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle.kts | 21 + android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle.kts | 25 + android_old/.gitignore | 13 + android_old/app/build.gradle | 58 ++ android_old/app/src/debug/AndroidManifest.xml | 7 + android_old/app/src/main/AndroidManifest.xml | 45 ++ .../kotlin/com/example/hym_ui/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + android_old/build.gradle | 32 + android_old/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android_old/settings.gradle | 25 + pubspec.lock | 638 +++--------------- pubspec.yaml | 5 +- 41 files changed, 616 insertions(+), 553 deletions(-) create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/com/example/crab_ui/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle.kts create mode 100644 android_old/.gitignore create mode 100644 android_old/app/build.gradle create mode 100644 android_old/app/src/debug/AndroidManifest.xml create mode 100644 android_old/app/src/main/AndroidManifest.xml create mode 100644 android_old/app/src/main/kotlin/com/example/hym_ui/MainActivity.kt create mode 100644 android_old/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android_old/app/src/main/res/drawable/launch_background.xml create mode 100644 android_old/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android_old/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android_old/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android_old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android_old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android_old/app/src/main/res/values-night/styles.xml create mode 100644 android_old/app/src/main/res/values/styles.xml create mode 100644 android_old/app/src/profile/AndroidManifest.xml create mode 100644 android_old/build.gradle create mode 100644 android_old/gradle.properties create mode 100644 android_old/gradle/wrapper/gradle-wrapper.properties create mode 100644 android_old/settings.gradle diff --git a/.metadata b/.metadata index 6eb54a1..c9704a8 100644 --- a/.metadata +++ b/.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 diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c908258 --- /dev/null +++ b/android/.gitignore @@ -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 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..86eb85d --- /dev/null +++ b/android/app/build.gradle.kts @@ -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 = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6023d1e --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/crab_ui/MainActivity.kt b/android/app/src/main/kotlin/com/example/crab_ui/MainActivity.kt new file mode 100644 index 0000000..7ea6dde --- /dev/null +++ b/android/app/src/main/kotlin/com/example/crab_ui/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.crab_ui + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..1cb7aa2 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..8403758 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..360a160 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5fac679 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..2f2f3f0 --- /dev/null +++ b/android/build.gradle.kts @@ -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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..b7cda7b --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..dcc7e10 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..8ddb35d --- /dev/null +++ b/android/settings.gradle.kts @@ -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") diff --git a/android_old/.gitignore b/android_old/.gitignore new file mode 100644 index 0000000..5d99765 --- /dev/null +++ b/android_old/.gitignore @@ -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 diff --git a/android_old/app/build.gradle b/android_old/app/build.gradle new file mode 100644 index 0000000..02e20bc --- /dev/null +++ b/android_old/app/build.gradle @@ -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 = "../.." +} diff --git a/android_old/app/src/debug/AndroidManifest.xml b/android_old/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android_old/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android_old/app/src/main/AndroidManifest.xml b/android_old/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d2914b1 --- /dev/null +++ b/android_old/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android_old/app/src/main/kotlin/com/example/hym_ui/MainActivity.kt b/android_old/app/src/main/kotlin/com/example/hym_ui/MainActivity.kt new file mode 100644 index 0000000..4a7c91b --- /dev/null +++ b/android_old/app/src/main/kotlin/com/example/hym_ui/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.hym_ui + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android_old/app/src/main/res/drawable-v21/launch_background.xml b/android_old/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..1cb7aa2 --- /dev/null +++ b/android_old/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android_old/app/src/main/res/drawable/launch_background.xml b/android_old/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..8403758 --- /dev/null +++ b/android_old/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android_old/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android_old/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/android_old/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android_old/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/android_old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android_old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/android_old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android_old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/android_old/app/src/main/res/values-night/styles.xml b/android_old/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..360a160 --- /dev/null +++ b/android_old/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android_old/app/src/main/res/values/styles.xml b/android_old/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5fac679 --- /dev/null +++ b/android_old/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android_old/app/src/profile/AndroidManifest.xml b/android_old/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android_old/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android_old/build.gradle b/android_old/build.gradle new file mode 100644 index 0000000..fefa38b --- /dev/null +++ b/android_old/build.gradle @@ -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 +} diff --git a/android_old/gradle.properties b/android_old/gradle.properties new file mode 100644 index 0000000..0d7fbd5 --- /dev/null +++ b/android_old/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android_old/gradle/wrapper/gradle-wrapper.properties b/android_old/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4abf038 --- /dev/null +++ b/android_old/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/android_old/settings.gradle b/android_old/settings.gradle new file mode 100644 index 0000000..0f10e04 --- /dev/null +++ b/android_old/settings.gradle @@ -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" diff --git a/pubspec.lock b/pubspec.lock index f4b7708..fe96027 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,106 +5,58 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" + sha256: "0511d6be23b007e95105ae023db599aea731df604608978dada7f9faf2637623" url: "https://pub.dev" source: hosted - version: "1.5.8" + version: "1.6.4" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" - url: "https://pub.dev" - source: hosted - version: "0.1.21" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" - cached_network_image: - dependency: transitive - description: - name: cached_network_image - sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" - url: "https://pub.dev" - source: hosted - version: "3.4.0" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7 - url: "https://pub.dev" - source: hosted - version: "4.1.0" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" - url: "https://pub.dev" - source: hosted - version: "1.3.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" - chewie: - dependency: transitive - description: - name: chewie - sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - chewie_audio: - dependency: transitive - description: - name: chewie_audio - sha256: "73948a8b9841d050433af3498a1f8b11320bd5a2cd70b449bdbe16d4405e97c5" - url: "https://pub.dev" - source: hosted - version: "1.5.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" convert: dependency: transitive description: @@ -121,38 +73,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" - url: "https://pub.dev" - source: hosted - version: "0.17.3" - cupertino_icons: - dependency: transitive - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dio: - dependency: transitive - description: - name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" - url: "https://pub.dev" - source: hosted - version: "5.8.0+1" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.dev" - source: hosted - version: "2.1.1" encrypt: dependency: "direct main" description: @@ -173,119 +93,31 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" - file_saver: - dependency: "direct main" - description: - name: file_saver - sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e" - url: "https://pub.dev" - source: hosted - version: "0.2.14" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" + version: "7.0.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea - url: "https://pub.dev" - source: hosted - version: "3.4.0" - flutter_html: - dependency: transitive - description: - name: flutter_html - sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_all: - dependency: "direct main" - description: - name: flutter_html_all - sha256: "5b4c449df76ecd186bea55414206c21bde83090d4b54d14caed78823718b7f1b" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_audio: - dependency: transitive - description: - name: flutter_html_audio - sha256: "94ae28ab56a8d556b7c5409e3eb59ca5215812bda87c67ddfa768812b76f8511" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_iframe: - dependency: transitive - description: - name: flutter_html_iframe - sha256: "979405fafcbd29c930bf96d9f3f0ade9d87dfd567a03180b13424a0e89a5de46" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_math: - dependency: transitive - description: - name: flutter_html_math - sha256: "7371f2621b77c66399e50b9fd5ff0eb2475c8c581af68e3eb21409c49a811211" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_svg: - dependency: transitive - description: - name: flutter_html_svg - sha256: "793be10cfa6fd0925a7adde9f58d73d2ce6a84c02ae12d01a985244f55ee631a" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_table: - dependency: transitive - description: - name: flutter_html_table - sha256: e20c72d67ea2512e7b4949f6f7dd13d004e773b0f82c586a21f895e6bd90383c - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_html_video: - dependency: transitive - description: - name: flutter_html_video - sha256: ecc8bcc614dd8a8286d32ace462481817660f5a7e854c663c1fcb3424fc3be89 - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" flutter_layout_grid: dependency: "direct overridden" description: @@ -314,10 +146,10 @@ packages: dependency: transitive description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -328,78 +160,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_widget_from_html: - dependency: "direct main" - description: - name: flutter_widget_from_html - sha256: "8d2a9a7979a9c1a5d866d1f4134d2ec2cca78716c112c76803d6a552281405cc" - url: "https://pub.dev" - source: hosted - version: "0.10.6" - flutter_widget_from_html_core: - dependency: transitive - description: - name: flutter_widget_from_html_core - sha256: "22140caa191cb4bba0fe4d5e4ad875c7e8a9ba47d61517f56d733019cf76396d" - url: "https://pub.dev" - source: hosted - version: "0.10.6" - fwfh_cached_network_image: - dependency: transitive - description: - name: fwfh_cached_network_image - sha256: "3de22aa3a6943c968e0d9fbcba4463b3dbbf7103171d62c84b6c672fb83eebdf" - url: "https://pub.dev" - source: hosted - version: "0.7.0+7" - fwfh_chewie: - dependency: transitive - description: - name: fwfh_chewie - sha256: "0b51a1c976bb74da5e8e45d545c74cb54a7168ad3938dd77103a7aee485f55fa" - url: "https://pub.dev" - source: hosted - version: "0.7.1+4" - fwfh_just_audio: - dependency: transitive - description: - name: fwfh_just_audio - sha256: "237b93a4cb9f0495a4b51940f361adda2a5abd57231dd44f07459db00144a6cd" - url: "https://pub.dev" - source: hosted - version: "0.9.0+3" - fwfh_svg: - dependency: transitive - description: - name: fwfh_svg - sha256: c6bb6b513f7ce2766aba76d7276caf9a96b6fee729ac3a492c366a42f82ef02e - url: "https://pub.dev" - source: hosted - version: "0.8.2" - fwfh_url_launcher: - dependency: transitive - description: - name: fwfh_url_launcher - sha256: b9f5d55a5ae2c2c07243ba33f7ba49ac9544bdb2f4c16d8139df9ccbebe3449c - url: "https://pub.dev" - source: hosted - version: "0.9.1" - fwfh_webview: - dependency: transitive - description: - name: fwfh_webview - sha256: "90a8dda0695403cf57abd7e8b83f6fb1f1a12933930a0bf9cac7cafb06e06a18" - url: "https://pub.dev" - source: hosted - version: "0.9.0+2" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" http: dependency: "direct main" description: @@ -412,10 +172,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" intl: dependency: "direct main" description: @@ -428,50 +188,26 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" - just_audio: - dependency: transitive - description: - name: just_audio - sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b - url: "https://pub.dev" - source: hosted - version: "0.9.37" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" - url: "https://pub.dev" - source: hosted - version: "4.3.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70" - url: "https://pub.dev" - source: hosted - version: "0.4.9" + version: "0.7.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -488,38 +224,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - list_counter: - dependency: transitive - description: - name: list_counter - sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 - url: "https://pub.dev" - source: hosted - version: "1.0.2" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" mime: dependency: "direct main" description: @@ -536,30 +264,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: transitive description: @@ -572,18 +292,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.9" + version: "2.2.17" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -612,18 +332,18 @@ packages: dependency: "direct main" description: name: pdfrx - sha256: "29c7b03d27d647c80da8cc08bd1256c74df90e5640fdd676646e4bd04f90553a" + sha256: "90747e916a64366b8beb69e9ac175e9134df520110543ab284b839102bf4b7e1" url: "https://pub.dev" source: hosted - version: "1.0.94" + version: "1.1.29" petitparser: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" photo_view: dependency: "direct main" description: @@ -636,10 +356,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -692,42 +412,42 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" quiver: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" rxdart: dependency: transitive description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -756,10 +476,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -772,87 +492,63 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d - url: "https://pub.dev" - source: hosted - version: "2.3.3+1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" - url: "https://pub.dev" - source: hosted - version: "2.5.4" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" synchronized: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.4" tuple: dependency: transitive description: @@ -865,10 +561,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" url_launcher: dependency: transitive description: @@ -881,34 +577,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.8" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -921,50 +617,42 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: a36e2d7981122fa185006b216eb6b5b97ede3f9a54b7a511bc966971ab98d049 + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.2" - uuid: - dependency: transitive - description: - name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" - url: "https://pub.dev" - source: hosted - version: "4.4.2" + version: "3.1.4" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -973,94 +661,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - video_player: - dependency: transitive - description: - name: video_player - sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d - url: "https://pub.dev" - source: hosted - version: "2.9.1" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: b6f0a6d241e4a3435806cb7cb78cb666db8889c1866e432b6acd204707b3ac01 - url: "https://pub.dev" - source: hosted - version: "2.5.3" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c - url: "https://pub.dev" - source: hosted - version: "2.6.1" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" - url: "https://pub.dev" - source: hosted - version: "6.2.2" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" - url: "https://pub.dev" - source: hosted - version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" - wakelock: - dependency: transitive - description: - name: wakelock - sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db" - url: "https://pub.dev" - source: hosted - version: "0.6.2" - wakelock_macos: - dependency: transitive - description: - name: wakelock_macos - sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - wakelock_platform_interface: - dependency: transitive - description: - name: wakelock_platform_interface - sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621" - url: "https://pub.dev" - source: hosted - version: "0.3.0" - wakelock_web: - dependency: transitive - description: - name: wakelock_web - sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - wakelock_windows: - dependency: transitive - description: - name: wakelock_windows - sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567" - url: "https://pub.dev" - source: hosted - version: "0.2.1" + version: "15.0.0" web: dependency: "direct main" description: @@ -1069,54 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - webview_flutter: - dependency: transitive - description: - name: webview_flutter - sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522" - url: "https://pub.dev" - source: hosted - version: "4.8.0" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9 - url: "https://pub.dev" - source: hosted - version: "3.16.6" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d - url: "https://pub.dev" - source: hosted - version: "2.10.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: "9c62cc46fa4f2d41e10ab81014c1de470a6c6f26051a2de32111b2ee55287feb" - url: "https://pub.dev" - source: hosted - version: "3.14.0" - win32: - dependency: transitive - description: - name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 - url: "https://pub.dev" - source: hosted - version: "3.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -1126,5 +694,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8de6880..d0633d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,14 +12,12 @@ dependencies: flutter: sdk: flutter http: 1.2.2 - flutter_html_all: 3.0.0-beta.2 - flutter_widget_from_html: ^0.10.0 shared_preferences: ^2.0.6 encrypt: ^5.0.0 pointycastle: ^3.4.0 mime: ^1.0.3 pointer_interceptor: ^0.10.1+2 - file_saver: ^0.2.14 + english_words: ^4.0.0 @@ -39,6 +37,7 @@ dependency_overrides: flutter_layout_grid: 2.0.7 flutter_math_fork: 0.7.2 + flutter: uses-material-design: true assets: From 152d74449805559cf9999fb70ec12799b7b314e1 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:12:31 -0400 Subject: [PATCH 02/64] working on android, and attachment widget interface with stub, android, and web implementations wip --- lib/attachmentWidget.dart | 106 +-------- lib/attachmentWidgetAndroid.dart | 16 ++ lib/attachmentWidgetStub.dart | 14 ++ lib/attachmentWidgetWeb.dart | 101 +++++++++ pubspec.lock | 360 +++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 6 files changed, 495 insertions(+), 103 deletions(-) create mode 100644 lib/attachmentWidgetAndroid.dart create mode 100644 lib/attachmentWidgetStub.dart create mode 100644 lib/attachmentWidgetWeb.dart diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index 5146d9c..c40eae1 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -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: [ - 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'; \ No newline at end of file diff --git a/lib/attachmentWidgetAndroid.dart b/lib/attachmentWidgetAndroid.dart new file mode 100644 index 0000000..72c3cd8 --- /dev/null +++ b/lib/attachmentWidgetAndroid.dart @@ -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") + ); + } +} + diff --git a/lib/attachmentWidgetStub.dart b/lib/attachmentWidgetStub.dart new file mode 100644 index 0000000..5bb6c14 --- /dev/null +++ b/lib/attachmentWidgetStub.dart @@ -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") + ); + } +} \ No newline at end of file diff --git a/lib/attachmentWidgetWeb.dart b/lib/attachmentWidgetWeb.dart new file mode 100644 index 0000000..b8b260b --- /dev/null +++ b/lib/attachmentWidgetWeb.dart @@ -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: [ + 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), + ) + ], + ), + ), + ), + ])); + } +} diff --git a/pubspec.lock b/pubspec.lock index fe96027..13a53ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" + url: "https://pub.dev" + source: hosted + version: "0.1.25" boolean_selector: dependency: transitive description: @@ -33,6 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -41,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + chewie: + dependency: transitive + description: + name: chewie + sha256: "4d9554a8f87cc2dc6575dfd5ad20a4375015a29edd567fd6733febe6365e2566" + url: "https://pub.dev" + source: hosted + version: "1.11.3" clock: dependency: transitive description: @@ -73,6 +113,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" encrypt: dependency: "direct main" description: @@ -113,11 +177,27 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_layout_grid: dependency: "direct overridden" description: @@ -160,6 +240,78 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "0dfebf7417df2149de93926520c703db9be0c9017e60dc5cf43cebed37f4d11e" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + sha256: f77ea1aa1ba29a38fcce04483f44f12382f541b9e8c2150df37166c23bbbd30f + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "8f4896109ff3e42424ccacf9058ba3afe5d575b58946c8ac646ac85ae882ce23" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: "1ce7c56894db19881a997813b933835dec142878431370c0eb40f1f878396a25" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: "17816168de1fd180fd3d1fd4500e23136630a248a6889b553e2d2067e133c1a6" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "82f3eb378186fe39b3e2e01ed48a1830d34b0b9a237d951077e74ff0d99e2ac3" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: "5cf1b1baa16740abaef8eb41a8e16ba430295d5ec20b880e4cb94e2924774f0a" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: "894aa7d98ebdc2d86d79ac2309173043dec7f102575de87bf9626ddb26104e49" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: "direct main" description: @@ -192,6 +344,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2" + just_audio: + dependency: transitive + description: + name: just_audio + sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e + url: "https://pub.dev" + source: hosted + version: "0.9.46" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" + url: "https://pub.dev" + source: hosted + version: "4.5.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" leak_tracker: dependency: transitive description: @@ -224,6 +400,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -264,6 +448,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: transitive description: @@ -501,6 +709,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -629,6 +885,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_graphics: dependency: transitive description: @@ -661,6 +925,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: transitive + description: + name: video_player + sha256: "7d78f0cfaddc8c19d4cb2d3bebe1bfef11f2103b0a03e5398b303a1bf65eeb14" + url: "https://pub.dev" + source: hosted + version: "2.9.5" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "28dcc4122079f40f93a0965b3679aff1a5f4251cf79611bd8011f937eb6b69de" + url: "https://pub.dev" + source: hosted + version: "2.8.4" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f" + url: "https://pub.dev" + source: hosted + version: "2.7.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 + url: "https://pub.dev" + source: hosted + version: "6.3.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba + url: "https://pub.dev" + source: hosted + version: "2.3.5" vm_service: dependency: transitive description: @@ -669,6 +973,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 + url: "https://pub.dev" + source: hosted + version: "1.3.2" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 + url: "https://pub.dev" + source: hosted + version: "1.2.3" web: dependency: "direct main" description: @@ -677,6 +997,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba + url: "https://pub.dev" + source: hosted + version: "4.13.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: f6e6afef6e234801da77170f7a1847ded8450778caf2fe13979d140484be3678 + url: "https://pub.dev" + source: hosted + version: "4.7.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "7cb32b21825bd65569665c32bb00a34ded5779786d6201f5350979d2d529940d" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3 + url: "https://pub.dev" + source: hosted + version: "3.22.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.dev" + source: hosted + version: "5.13.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d0633d2..d3e7a93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: pdfrx: ^1.0.94 photo_view: ^0.15.0 web: ^1.1.1 + flutter_widget_from_html: ^0.16.0 dev_dependencies: flutter_test: From 04a871a293edf6ec3880149e0b42da8557844818 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:12:59 -0400 Subject: [PATCH 03/64] collapsable implementation for all platforms, WIP --- lib/collapsableEmails.dart | 135 +----------------------------- lib/collapsableEmailsAndroid.dart | 48 +++++++++++ lib/collapsableEmailsStub.dart | 23 +++++ lib/collapsableEmailsWeb.dart | 132 +++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 132 deletions(-) create mode 100644 lib/collapsableEmailsAndroid.dart create mode 100644 lib/collapsableEmailsStub.dart create mode 100644 lib/collapsableEmailsWeb.dart diff --git a/lib/collapsableEmails.dart b/lib/collapsableEmails.dart index ceed92a..e8b144d 100644 --- a/lib/collapsableEmails.dart +++ b/lib/collapsableEmails.dart @@ -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 thread; // email id's in the form xyz@gmail.com - final List threadHTML; - final String threadIDs; - - CollapsableEmails( - {required this.thread, - required this.threadHTML, - required this.threadIDs}); - - @override - State createState() => _CollapsableEmailsState(); -} - -class _CollapsableEmailsState extends State { - List emailsHTML = []; //html of the emails in the thread - // build attachments with the forldar name and id - Set _expandedEmails = {}; //open emails - List viewtypeIDs = []; //IDs of the viewtypes, order matters - List heightOfViewTypes = []; //the height of each viewtype - List emailsInThread = []; - bool _isLoaded = false; - - @override - void initState() { - // TODO: implement initState - super.initState(); - _registerViewFactory(widget.threadHTML); - _serializableData(widget.threadIDs); - } - - void _registerViewFactory(List 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'; \ No newline at end of file diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart new file mode 100644 index 0000000..f2cdf01 --- /dev/null +++ b/lib/collapsableEmailsAndroid.dart @@ -0,0 +1,48 @@ +import 'structs.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; + +class CollapsableEmails extends StatefulWidget { + final List thread; // email id's in the form xyz@gmail.com + final List threadHTML; + final String threadIDs; + + CollapsableEmails( + {required this.thread, + required this.threadHTML, + required this.threadIDs}); + + @override + State createState() => _CollapsableEmailsState(); +} + +class _CollapsableEmailsState extends State { + List emailsHTML = []; //html of the emails in the thread + // build attachments with the forldar name and id + Set _expandedEmails = {}; //open emails + List viewtypeIDs = []; //IDs of the viewtypes, order matters + List heightOfViewTypes = []; //the height of each viewtype + List emailsInThread = []; + bool _isLoaded = false; + + @override + void initState() { + super.initState(); + //html + } + + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: [ + HtmlWidget( + widget.threadHTML[0], + // renderMode: RenderMode.listView, + ) + ] + ) + ); + } +} diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart new file mode 100644 index 0000000..7c89a88 --- /dev/null +++ b/lib/collapsableEmailsStub.dart @@ -0,0 +1,23 @@ +import 'structs.dart'; +import 'package:flutter/material.dart'; + +class CollapsableEmails extends StatefulWidget { + final List thread; // email id's in the form xyz@gmail.com + final List threadHTML; + final String threadIDs; + + CollapsableEmails( + {required this.thread, + required this.threadHTML, + required this.threadIDs}); + + @override + State createState() => _CollapsableEmailsState(); +} + +class _CollapsableEmailsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold(body: Text("collapsable stud")); + } +} diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart new file mode 100644 index 0000000..ee8e4ea --- /dev/null +++ b/lib/collapsableEmailsWeb.dart @@ -0,0 +1,132 @@ +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 thread; // email id's in the form xyz@gmail.com + final List threadHTML; + final String threadIDs; + + CollapsableEmails( + {required this.thread, + required this.threadHTML, + required this.threadIDs}); + + @override + State createState() => _CollapsableEmailsState(); +} + +class _CollapsableEmailsState extends State { + List emailsHTML = []; //html of the emails in the thread + // build attachments with the forldar name and id + Set _expandedEmails = {}; //open emails + List viewtypeIDs = []; //IDs of the viewtypes, order matters + List heightOfViewTypes = []; //the height of each viewtype + List emailsInThread = []; + bool _isLoaded = false; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _registerViewFactory(widget.threadHTML); + _serializableData(widget.threadIDs); + } + + void _registerViewFactory(List 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()); + } +} From 8eeb0b013d4af27977ca1a01c1a4d7eef0a8a22b Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:13:17 -0400 Subject: [PATCH 04/64] attachment download WIP --- lib/attachamentDownloadStub.dart | 7 +++++++ lib/attachmentDownload.dart | 18 +++--------------- lib/attachmentDownloadAndroid.dart | 7 +++++++ lib/attachmentDownloadWeb.dart | 12 ++++++++++++ 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 lib/attachamentDownloadStub.dart create mode 100644 lib/attachmentDownloadAndroid.dart create mode 100644 lib/attachmentDownloadWeb.dart diff --git a/lib/attachamentDownloadStub.dart b/lib/attachamentDownloadStub.dart new file mode 100644 index 0000000..5daf854 --- /dev/null +++ b/lib/attachamentDownloadStub.dart @@ -0,0 +1,7 @@ +import 'structs.dart'; + +class Attachmentdownload { + Future saveFile(AttachmentResponse attachment) async { + print("stub attachment download"); + } +} diff --git a/lib/attachmentDownload.dart b/lib/attachmentDownload.dart index 63364ba..f1f8f86 100644 --- a/lib/attachmentDownload.dart +++ b/lib/attachmentDownload.dart @@ -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 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'; \ No newline at end of file diff --git a/lib/attachmentDownloadAndroid.dart b/lib/attachmentDownloadAndroid.dart new file mode 100644 index 0000000..603253c --- /dev/null +++ b/lib/attachmentDownloadAndroid.dart @@ -0,0 +1,7 @@ +import 'structs.dart'; + +class Attachmentdownload { + Future saveFile(AttachmentResponse attachment) async { + print("android attachment download"); + } +} diff --git a/lib/attachmentDownloadWeb.dart b/lib/attachmentDownloadWeb.dart new file mode 100644 index 0000000..cb4954c --- /dev/null +++ b/lib/attachmentDownloadWeb.dart @@ -0,0 +1,12 @@ +// import 'structs.dart'; +// import 'package:file_saver/file_saver.dart'; + +// class Attachmentdownload { +// Future 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) +// ); +// } +// } From 36e6cbb2f115a0f572650bb7162e64d64eb9289c Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:13:45 -0400 Subject: [PATCH 05/64] sonic implementation in all platforms --- lib/SonicEmailViewAndroid.dart | 19 +++++ lib/SonicEmailViewStub.dart | 22 +++++ lib/SonicEmailViewWeb.dart | 145 ++++++++++++++++++++++++++++++++ lib/sonicEmailView.dart | 148 +-------------------------------- 4 files changed, 189 insertions(+), 145 deletions(-) create mode 100644 lib/SonicEmailViewAndroid.dart create mode 100644 lib/SonicEmailViewStub.dart create mode 100644 lib/SonicEmailViewWeb.dart diff --git a/lib/SonicEmailViewAndroid.dart b/lib/SonicEmailViewAndroid.dart new file mode 100644 index 0000000..fc2b67c --- /dev/null +++ b/lib/SonicEmailViewAndroid.dart @@ -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 { + @override + Widget build(BuildContext context) { + return Scaffold(body: Text("sonic email android")); + } +} diff --git a/lib/SonicEmailViewStub.dart b/lib/SonicEmailViewStub.dart new file mode 100644 index 0000000..0307a15 --- /dev/null +++ b/lib/SonicEmailViewStub.dart @@ -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 { + @override + Widget build(BuildContext context) { + return Scaffold( + body:Text("sonic email stub") + ); + } + +} diff --git a/lib/SonicEmailViewWeb.dart b/lib/SonicEmailViewWeb.dart new file mode 100644 index 0000000..3ad8f76 --- /dev/null +++ b/lib/SonicEmailViewWeb.dart @@ -0,0 +1,145 @@ +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 { + String viewTypeIDs = ""; + int heightOFViewtype = 0; + bool _isLoaded = false; + + void _scrollToNumber(String spanId) { + AugmentClasses.handleJump(spanId); + } + + @override + void initState() { + super.initState(); + _init(); + } + + Future _init() async { + await _registerViewFactory(widget.emailHTML); + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + Future _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(), + ); + } +} diff --git a/lib/sonicEmailView.dart b/lib/sonicEmailView.dart index bfe0bbe..aeb7301 100644 --- a/lib/sonicEmailView.dart +++ b/lib/sonicEmailView.dart @@ -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 { - String viewTypeIDs = ""; - int heightOFViewtype = 0; - bool _isLoaded = false; - - void _scrollToNumber(String spanId) { - AugmentClasses.handleJump(spanId); - } - - @override - void initState() { - super.initState(); - _init(); - } - - Future _init() async { - await _registerViewFactory(widget.emailHTML); - if (!mounted) return; - setState(() { - _isLoaded = true; - }); - } - - Future _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'; \ No newline at end of file From 50528de0f2f5216831f38dde86bd1e0916720906 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:14:46 -0400 Subject: [PATCH 06/64] email view , WIP --- lib/emailView.dart | 3 + lib/emailViewAndroid.dart | 95 +++++++++++++++ lib/emailViewStub.dart | 39 ++++++ lib/emailViewWeb.dart | 241 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 378 insertions(+) create mode 100644 lib/emailView.dart create mode 100644 lib/emailViewAndroid.dart create mode 100644 lib/emailViewStub.dart create mode 100644 lib/emailViewWeb.dart diff --git a/lib/emailView.dart b/lib/emailView.dart new file mode 100644 index 0000000..61c9bf8 --- /dev/null +++ b/lib/emailView.dart @@ -0,0 +1,3 @@ +export 'emailViewStub.dart' + if (dart.library.io) 'emailViewAndroid.dart' + if (dart.library.js_interop) 'emailViewWeb.dart'; \ No newline at end of file diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart new file mode 100644 index 0000000..d6d6981 --- /dev/null +++ b/lib/emailViewAndroid.dart @@ -0,0 +1,95 @@ +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 emailContent; + final String from; + final String name; + final String to; + final String subject; + final String date; + final String id; + final List 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 { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.name), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + 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, + threadHTML: widget.emailContent, + threadIDs: widget.id, + ), + ), + ], + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/emailViewStub.dart b/lib/emailViewStub.dart new file mode 100644 index 0000000..5c5f657 --- /dev/null +++ b/lib/emailViewStub.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + + +class EmailView extends StatefulWidget { + final List emailContent; + final String from; + final String name; + final String to; + final String subject; + final String date; + final String id; + final List 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 { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text(" emailview stub, not supported") + ) + ); + } + +} \ No newline at end of file diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart new file mode 100644 index 0000000..08551eb --- /dev/null +++ b/lib/emailViewWeb.dart @@ -0,0 +1,241 @@ +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 emailContent; + final String from; + final String name; + final String to; + final String subject; + final String date; + final String id; + final List 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 { + //html css rendering thing + late Key iframeKey; + late String currentContent; + late String viewTypeId; //make this a list too??? + Future>>? _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 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 _registerViewFactory(List 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""" + //

Welcome to My Website

+ //

This is a simple HTML page.

+ //

What is HTML?

+ //

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).

+ //

Here's a simple list:

+ //
    + //
  • HTML elements are the building blocks of HTML pages
  • + //
  • HTML uses tags like <tag> to organize and format content
  • + //
  • CSS is used with HTML to style pages
  • + //
+ //

Copyright © 2023

+ // """); + // 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>>( + // 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), + // ), + // ), + // ), + // ), + // ), + ], + )); + } +} From 7a6eea64e110d14da3fa730a38567e062666a845 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 23 May 2025 16:15:26 -0400 Subject: [PATCH 07/64] cleaning and adjusting for package --- lib/api_service.dart | 246 +------------------------------------------ lib/augment.dart | 7 -- lib/contact.dart | 4 +- lib/email.dart | 2 +- lib/home_page.dart | 63 ++++++----- 5 files changed, 40 insertions(+), 282 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index b0b92ea..4769377 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -4,19 +4,12 @@ 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 = ""; @@ -422,237 +415,4 @@ class ApiService { // return []; // } -} - -class EmailView extends StatefulWidget { - final List emailContent; - final String from; - final String name; - final String to; - final String subject; - final String date; - final String id; - final List 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 { - //html css rendering thing - late Key iframeKey; - late String currentContent; - late String viewTypeId; //make this a list too??? - Future>>? _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 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 _registerViewFactory(List 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""" - //

Welcome to My Website

- //

This is a simple HTML page.

- //

What is HTML?

- //

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).

- //

Here's a simple list:

- //
    - //
  • HTML elements are the building blocks of HTML pages
  • - //
  • HTML uses tags like <tag> to organize and format content
  • - //
  • CSS is used with HTML to style pages
  • - //
- //

Copyright © 2023

- // """); - // 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>>( - // 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), - // ), - // ), - // ), - // ), - // ), - ], - )); - } -} +} \ No newline at end of file diff --git a/lib/augment.dart b/lib/augment.dart index b957740..509b3b0 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,14 +1,7 @@ -// 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'; diff --git a/lib/contact.dart b/lib/contact.dart index 73d60fd..32b4dae 100644 --- a/lib/contact.dart +++ b/lib/contact.dart @@ -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}); diff --git a/lib/email.dart b/lib/email.dart index 5dbf1a6..ff4d600 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -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 emails; diff --git a/lib/home_page.dart b/lib/home_page.dart index b4878b0..903a8a4 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -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'; @@ -266,40 +266,45 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 800, - height: 40, - child: TextField( - decoration: InputDecoration( - hintText: 'Search...', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.search), + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 800, + ), + child: SizedBox( + height: 40, + child: TextField( + decoration: InputDecoration( + hintText: 'Search...', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.search), + ), + onSubmitted: (value) { + if (value.isNotEmpty) { + _performSearch(value, _selectedOption); + } + //this is the input box i mentioned + // if (value == '') { + // setState(() { + // querySearches = false; + // }); + // } + // Future> results = apiService + // .sonicSearch('INBOX', 20, 0, value); + // // print(value); + // print(results); + // setState(() { + // querySearches = true; + // }); + }, + ), ), - onSubmitted: (value) { - if (value.isNotEmpty) { - _performSearch(value, _selectedOption); - } - //this is the input box i mentioned - // if (value == '') { - // setState(() { - // querySearches = false; - // }); - // } - // Future> results = apiService - // .sonicSearch('INBOX', 20, 0, value); - // // print(value); - // print(results); - // setState(() { - // querySearches = true; - // }); - }, ), ), SizedBox( - width: 16, + width: 8, ), Container( - width: 80, height: 40, child: ElevatedButton( onPressed: _showOptionsSearchDialog, From 37c7e6a9353c056a0db6766d13f3ced3760b188d Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 24 May 2025 19:56:15 -0400 Subject: [PATCH 08/64] viewspecs, hiding right purple number --- lib/collapsableEmailsWeb.dart | 104 +++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index ee8e4ea..53b2d41 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -27,13 +27,14 @@ class _CollapsableEmailsState extends State { List heightOfViewTypes = []; //the height of each viewtype List emailsInThread = []; bool _isLoaded = false; + static bool _isListenerRegistered = false; @override void initState() { - // TODO: implement initState super.initState(); _registerViewFactory(widget.threadHTML); - _serializableData(widget.threadIDs); + _serializableData(widget.threadIDs); // this + _keyListener(); } void _registerViewFactory(List currentContent) async { @@ -88,45 +89,68 @@ class _CollapsableEmailsState extends State { }); } + void _keyListener() { + if (_isListenerRegistered) return; + _isListenerRegistered = true; + web.window.document.addEventListener( + 'keydown', + ((web.Event event) { + final keyEvent = event as web.KeyboardEvent; + + if (keyEvent.key.toLowerCase() == 'k') { + print('You pressed the "k" key!'); + final leftPurpleNums = web.document.getElementsByClassName("right"); + + for (int i = 0; i < leftPurpleNums.length; i++) { + final currentElement = leftPurpleNums.item(i) as web.HTMLElement; + final opacity = web.window.getComputedStyle(currentElement).opacity; + + currentElement.style.opacity = + (opacity == '0') ? '1.0' : '0.0'; // works mostly + } + } + }).toJS, + ); + } + @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()); + 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) + SizedBox( + height: heightOfViewTypes[index].toDouble(), + child: HtmlElementView( + key: UniqueKey(), viewType: viewtypeIDs[index]), + ), + Divider(), + ], + ); + }, + ), + ) + ]) + : const Center(child: CircularProgressIndicator()); } } From 13004c6d499974ff97e4862cc299f0680b67b201 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 26 May 2025 14:29:37 -0400 Subject: [PATCH 09/64] LEFT RIGHT purple numbers on/off --- lib/collapsableEmailsWeb.dart | 57 ++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 53b2d41..af80fbf 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -28,6 +28,9 @@ class _CollapsableEmailsState extends State { List emailsInThread = []; bool _isLoaded = false; static bool _isListenerRegistered = false; + static bool left = true; + static bool right = true; + web.EventListener? _listener; @override void initState() { @@ -36,6 +39,14 @@ class _CollapsableEmailsState extends State { _serializableData(widget.threadIDs); // this _keyListener(); } + // @override + // void dispose() { + // if (_listener != null) { + // web.window.document.removeEventListener('keydown', _listener!); + // } + // super.dispose(); + // } + void _registerViewFactory(List currentContent) async { // setState(() { //update to do item per item @@ -89,30 +100,40 @@ class _CollapsableEmailsState extends State { }); } + void handleKeyDown(web.Event event) { + final keyEvent = event as web.KeyboardEvent; + + if (keyEvent.key == 'G') { + print('You pressed the "G" key!'); + final rightPurpleNums = web.document.getElementsByClassName("right"); + _CollapsableEmailsState.right = !_CollapsableEmailsState.right; + final newOpacity = _CollapsableEmailsState.right ? '1.0' : '0.0'; + for (int i = 0; i < rightPurpleNums.length; i++) { + final currentElement = rightPurpleNums.item(i) as web.HTMLElement; + currentElement.style.opacity = newOpacity; + } + } else if (keyEvent.key == 'H') { + print('You pressed the "H" key!'); + final leftPurpleNums = web.document.getElementsByClassName("left"); + _CollapsableEmailsState.left = !_CollapsableEmailsState.left; + final newOpacity = _CollapsableEmailsState.left ? '1.0' : '0.0'; + for (int i = 0; i < leftPurpleNums.length; i++) { + final currentElement = leftPurpleNums.item(i) as web.HTMLElement; + currentElement.style.opacity = newOpacity; + } + } + } + void _keyListener() { if (_isListenerRegistered) return; _isListenerRegistered = true; - web.window.document.addEventListener( - 'keydown', - ((web.Event event) { - final keyEvent = event as web.KeyboardEvent; - if (keyEvent.key.toLowerCase() == 'k') { - print('You pressed the "k" key!'); - final leftPurpleNums = web.document.getElementsByClassName("right"); - - for (int i = 0; i < leftPurpleNums.length; i++) { - final currentElement = leftPurpleNums.item(i) as web.HTMLElement; - final opacity = web.window.getComputedStyle(currentElement).opacity; - - currentElement.style.opacity = - (opacity == '0') ? '1.0' : '0.0'; // works mostly - } - } - }).toJS, - ); + // Convert the top-level function to JS-compatible + _listener = handleKeyDown.toJS; + web.window.document.addEventListener('keydown', _listener!); } + @override Widget build(BuildContext context) { return _isLoaded From b4f5abb7e1fe20b562605cd433e51bd038a2613c Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 26 May 2025 14:38:24 -0400 Subject: [PATCH 10/64] added on/off purple numbers 'n', 'm' --- lib/collapsableEmailsWeb.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index af80fbf..bb7e825 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -47,7 +47,6 @@ class _CollapsableEmailsState extends State { // super.dispose(); // } - void _registerViewFactory(List currentContent) async { // setState(() { //update to do item per item // each item to have itsviewtype ID @@ -121,6 +120,26 @@ class _CollapsableEmailsState extends State { final currentElement = leftPurpleNums.item(i) as web.HTMLElement; currentElement.style.opacity = newOpacity; } + } else if (keyEvent.key == 'm') { + print("you pressed 'm'"); + final purpleNums = web.document.getElementsByClassName("purplenumber"); + _CollapsableEmailsState.left = true; + _CollapsableEmailsState.right = true; + + for (int i = 0; i < purpleNums.length; i++) { + final currentElement = purpleNums.item(i) as web.HTMLElement; + currentElement.style.opacity = '1.0'; + } + } else if (keyEvent.key == 'n') { + print("you pressed 'n'"); + final purpleNums = web.document.getElementsByClassName("purplenumber"); + _CollapsableEmailsState.left = false; + _CollapsableEmailsState.right = false; + + for (int i = 0; i < purpleNums.length; i++) { + final currentElement = purpleNums.item(i) as web.HTMLElement; + currentElement.style.opacity = '0.0'; + } } } @@ -133,7 +152,6 @@ class _CollapsableEmailsState extends State { web.window.document.addEventListener('keydown', _listener!); } - @override Widget build(BuildContext context) { return _isLoaded From df6329397741a5d7e229c9ff9b1909ef2e729fe6 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 27 May 2025 23:38:37 -0400 Subject: [PATCH 11/64] tree data structure for the zooms of augment --- lib/collapsableEmailsWeb.dart | 60 +++++++++++++++++++++++++++++++++++ lib/structs.dart | 12 +++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index bb7e825..966a10e 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,4 +1,5 @@ import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; import 'dart:ui_web' as ui; @@ -31,6 +32,8 @@ class _CollapsableEmailsState extends State { static bool left = true; static bool right = true; web.EventListener? _listener; + List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; + List tagsCollected = []; @override void initState() { @@ -140,6 +143,9 @@ class _CollapsableEmailsState extends State { final currentElement = purpleNums.item(i) as web.HTMLElement; currentElement.style.opacity = '0.0'; } + } else if (keyEvent.key == 'w') { + print("you pressed 'w'"); + getTopLevel(); } } @@ -152,6 +158,60 @@ class _CollapsableEmailsState extends State { web.window.document.addEventListener('keydown', _listener!); } + void getTopLevel() { + print("started top"); + int highest = 0; + AugmentTree zoomTreeRoot = AugmentTree(); + // zoomTreeRoot.data = emailsHTML[0]; // whole thing + + while (highest < hirarchy.length - 1) { + var highestElement = web.document.querySelectorAll(hirarchy[highest]); + print("nodelist $highestElement"); + + if (highestElement.isNull || highestElement.length == 0) { + //from h1, h2, h3, ..., p. + highest += 1; + print(hirarchy[highest]); + } else { + AugmentTree newLevel = AugmentTree(); // list of children of each level + for (int i = 0; i < highestElement.length; i++) { + print(highestElement.item(i)?.textContent); + + tagsCollected + .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); + newLevel.children + .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); + } + // traverse to last node and add new level to it + // next + if (zoomTreeRoot.node == null) { + zoomTreeRoot.node = newLevel; + } else { + AugmentTree temp = zoomTreeRoot; + while (true) { + //get to the last and assign node = last node + if (temp.node != null) { + temp = temp.node!; + } else { + temp.node = newLevel; + break; + } + } + } + + highest += 1; + } + } + print("out safely"); + print(tagsCollected); //instead of a list make a tree + print(zoomTreeRoot.children); + print(zoomTreeRoot.node?.children); + print(zoomTreeRoot.node?.node?.children); + print(zoomTreeRoot.node?.node?.node?.children); + + + } + @override Widget build(BuildContext context) { return _isLoaded diff --git a/lib/structs.dart b/lib/structs.dart index c7e9da4..4672792 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -127,7 +127,8 @@ class AttachmentInfoList extends Iterable { AttachmentInfoList(this._attachments); factory AttachmentInfoList.fromJsonList(List> jsonList) { - return AttachmentInfoList(jsonList.map((json) => AttachmentInfo.fromJson(json)).toList()); + return AttachmentInfoList( + jsonList.map((json) => AttachmentInfo.fromJson(json)).toList()); } @override @@ -143,6 +144,13 @@ class AttachmentResponse { AttachmentResponse({required this.name, required this.data}); factory AttachmentResponse.fromJson(Map json) { - return AttachmentResponse(name: json["name"], data: Uint8List.fromList(List.from(json["data"]))); + return AttachmentResponse( + name: json["name"], + data: Uint8List.fromList(List.from(json["data"]))); } } + +class AugmentTree { + List children = []; + AugmentTree? node; +} From a1fde46aec454cc57bed49c940415da5fe7d792d Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 9 Jun 2025 16:37:38 -0400 Subject: [PATCH 12/64] data structure works --- lib/collapsableEmailsWeb.dart | 430 +++++++++++++++++++++++++++------- 1 file changed, 340 insertions(+), 90 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 966a10e..b748daf 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'package:web/web.dart' as web; @@ -5,6 +6,9 @@ import 'package:flutter/material.dart'; import 'dart:ui_web' as ui; 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 thread; // email id's in the form xyz@gmail.com @@ -33,22 +37,234 @@ class _CollapsableEmailsState extends State { static bool right = true; web.EventListener? _listener; List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; + Map hirarchyDict = { + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4, + "h5": 6, + "h6": 7, + "p": 8, + "ul": 8, + "li": 8, + }; + List tagsCollected = []; + String markdown = ''; + List> sentinel = []; + int level = 0; + AugmentTree root = AugmentTree(); @override void initState() { super.initState(); - _registerViewFactory(widget.threadHTML); - _serializableData(widget.threadIDs); // this + _markdownConverter(); + // _registerViewFactory(widget.threadHTML); + // _serializableData(widget.threadIDs); // this + _markdown2Tree(markdown); _keyListener(); + _buildForZooms(level); + } + + @override + void dispose() { + if (_listener != null) { + web.window.document.removeEventListener('keydown', _listener!); + _listener = null; + _isListenerRegistered = false; + } + super.dispose(); + } + + void _markdownConverter() async { + markdown = html2md.convert(widget.threadHTML[0]); + } + + 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 + print('is 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 { + tree.children.add(newNode); + } + // } else{ // so the node should go high + // _add2Tree(tree.children.last, node2add); + // } + + //maybe? + // } else { + // } + } 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; + } + + print("> ${node2add.tag}"); + + _add2Tree(tree.children.last, node2add); + } else if ((hirarchyDict[node2add.tag] ?? -1) == + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + print("equals??"); + tree.children.add(newNode); + newNode.parent = tree; + } + } + } + + void _markdown2Tree(String text) { + print("started markdown2tree"); + int highest = 0; + AugmentTree zoomTreeRoot = AugmentTree(); + final List nakedList = md.Document().parseLines(text.split('\n')); + List pList = []; + List h1List = []; + List h2List = []; + List h3List = []; + List h4List = []; + List h5List = []; + List h6List = []; + + for (var node in nakedList) { + //maybe do an add function, but isn't this it? + if (node is md.Element) { + // print(node.textContent); + AugmentTree temp = AugmentTree(); + temp.data = node.textContent; + temp.ogTag = node.tag; + if (node.tag == 'h1') { + h1List.add(node.textContent); + _add2Tree(zoomTreeRoot, node); + } else if (node.tag == 'h2') { + // i dont add any since i dont have it, maybe the function makes sense + h2List.add(node.textContent); + } else if (node.tag == 'h3') { + h3List.add(node.textContent); + root.children.add(temp); + _add2Tree(zoomTreeRoot, node); + } else if (node.tag == 'h4') { + h4List.add(node.textContent); //this broke it + _add2Tree(zoomTreeRoot, node); // change to temp + if (root.children.isNotEmpty) { + root.children.last.children.add(temp); + } + } else if (node.tag == 'h5') { + h5List.add(node.textContent); + print(node.textContent); + _add2Tree(zoomTreeRoot, node); + if (root.children.last.children.isNotEmpty) { + print("h5 index ${root.children.last.children.length}"); + root.children.last.children.last.children.add(temp); + print( + "h5 after index length ${root.children.last.children.last.children.length}"); + } + } else if (node.tag == 'h6') { + h6List.add(node.textContent); + if (root.children.last.children.isNotEmpty) { + print( + "h6 index ${root.children.last.children.last.children.length}"); + root.children.last.children.last.children.add(temp); + print(node.textContent); + _add2Tree(zoomTreeRoot, node); + } + // root.children.last.children.last.children.add(temp); + } else if (node.tag == 'p' || node.tag == 'ul' || node.tag == 'li') { + pList.add(node.textContent); + _add2Tree(zoomTreeRoot, node); // fix this + + if (root.children.isEmpty) { + root.children.add(temp); + } else if (root.children.last.children.isNotEmpty) { + //perhaps recursive + root.children.last.children.last.children.add(temp); + } + } + } + } + + this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList]; + sentinel.removeWhere((hList) => hList.isEmpty); + + print('algorithm adding: '); + print("first layer: ${zoomTreeRoot.children}"); + print("first node: ${zoomTreeRoot.children[0].data}"); //good + print("second layer: ${zoomTreeRoot.children.last.children}"); //good + print( + "first node: ${zoomTreeRoot.children.last.children.first.data}"); //good + for (int n = 1; n < zoomTreeRoot.children.last.children.length - 1; n++) { + //good + print(zoomTreeRoot.children.last.children[n].data); + } + print("last node: ${zoomTreeRoot.children.last.children.last.data}"); //good + print("third layer"); + for (int thirdLayer = 0; + thirdLayer < zoomTreeRoot.children.last.children.length; + thirdLayer++) { + print(zoomTreeRoot.children.last.children[thirdLayer].children); + } + print("third layer contents first"); //not sure + + print(zoomTreeRoot.children.last.children[5].children[0].data); //good + for (int contentsOf4 = 1; + contentsOf4 < zoomTreeRoot.children.last.children[5].children.length; + contentsOf4++) { + print(zoomTreeRoot.children.last.children[5].children[contentsOf4].data); + } + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + void handleKeyDownMD(web.Event event) async { + final keyEvent = event as web.KeyboardEvent; + + if (!mounted) return; + if (keyEvent.key == 'a') { + print("key a"); + setState(() { + level = (level - 1).clamp(0, sentinel.length - 1); + }); + // _buildForZooms(level + 1); //probably need a level? + } else if (keyEvent.key == "b") { + print("b"); + setState(() { + level = (level + 1).clamp(0, sentinel.length - 1); + }); + // _buildForZooms(level - 1); //probably need a level? + } } - // @override - // void dispose() { - // if (_listener != null) { - // web.window.document.removeEventListener('keydown', _listener!); - // } - // super.dispose(); - // } void _registerViewFactory(List currentContent) async { // setState(() { //update to do item per item @@ -102,7 +318,7 @@ class _CollapsableEmailsState extends State { }); } - void handleKeyDown(web.Event event) { + void handleKeyDownHTML(web.Event event) { final keyEvent = event as web.KeyboardEvent; if (keyEvent.key == 'G') { @@ -145,7 +361,7 @@ class _CollapsableEmailsState extends State { } } else if (keyEvent.key == 'w') { print("you pressed 'w'"); - getTopLevel(); + // getTopLevel(); } } @@ -154,62 +370,91 @@ class _CollapsableEmailsState extends State { _isListenerRegistered = true; // Convert the top-level function to JS-compatible - _listener = handleKeyDown.toJS; + // _listener = handleKeyDownHTML.toJS; + _listener = handleKeyDownMD.toJS; + web.window.document.addEventListener('keydown', _listener!); } - void getTopLevel() { - print("started top"); - int highest = 0; - AugmentTree zoomTreeRoot = AugmentTree(); - // zoomTreeRoot.data = emailsHTML[0]; // whole thing + // void getTopLevel() { + // print("started top"); + // int highest = 0; + // AugmentTree zoomTreeRoot = AugmentTree(); + // // zoomTreeRoot.data = emailsHTML[0]; // whole thing - while (highest < hirarchy.length - 1) { - var highestElement = web.document.querySelectorAll(hirarchy[highest]); - print("nodelist $highestElement"); + // while (highest < hirarchy.length - 1) { + // var highestElement = web.document.querySelectorAll(hirarchy[highest]); + // print("nodelist $highestElement"); - if (highestElement.isNull || highestElement.length == 0) { - //from h1, h2, h3, ..., p. - highest += 1; - print(hirarchy[highest]); - } else { - AugmentTree newLevel = AugmentTree(); // list of children of each level - for (int i = 0; i < highestElement.length; i++) { - print(highestElement.item(i)?.textContent); + // if (highestElement.isNull || highestElement.length == 0) { + // //from h1, h2, h3, ..., p. + // highest += 1; + // print(hirarchy[highest]); + // } else { + // AugmentTree newLevel = AugmentTree(); // list of children of each level + // for (int i = 0; i < highestElement.length; i++) { + // print(highestElement.item(i)?.textContent); - tagsCollected - .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); - newLevel.children - .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); - } - // traverse to last node and add new level to it - // next - if (zoomTreeRoot.node == null) { - zoomTreeRoot.node = newLevel; - } else { - AugmentTree temp = zoomTreeRoot; - while (true) { - //get to the last and assign node = last node - if (temp.node != null) { - temp = temp.node!; - } else { - temp.node = newLevel; - break; - } - } - } + // tagsCollected + // .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); + // newLevel.children + // .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); + // } + // // traverse to last node and add new level to it + // // next + // if (zoomTreeRoot.node == null) { + // zoomTreeRoot.node = newLevel; + // } else { + // AugmentTree temp = zoomTreeRoot; + // while (true) { + // //get to the last and assign node = last node + // if (temp.node != null) { + // temp = temp.node!; + // } else { + // temp.node = newLevel; + // break; + // } + // } + // } - highest += 1; - } + // highest += 1; + // } + // } + // print("out safely"); + // print(tagsCollected); //instead of a list make a tree + // print(zoomTreeRoot.children); + // print(zoomTreeRoot.node?.children); + // print(zoomTreeRoot.node?.node?.children); + // print(zoomTreeRoot.node?.node?.node?.children); + // } + + Widget _buildForZooms(int lvl) { + this.level = lvl; + if (lvl < 0 || lvl >= sentinel.length) { + return Center(child: Text("No content at level $lvl")); } - print("out safely"); - print(tagsCollected); //instead of a list make a tree - print(zoomTreeRoot.children); - print(zoomTreeRoot.node?.children); - print(zoomTreeRoot.node?.node?.children); - print(zoomTreeRoot.node?.node?.node?.children); - - + return ListView.builder( + itemCount: this.sentinel[level].length, + itemBuilder: (context, index) { + 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.surfaceTint, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: MarkdownBlock( + data: sentinel[level][index], // one string of markdown + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + ), + ); + }, + ); } @override @@ -217,38 +462,43 @@ class _CollapsableEmailsState extends State { 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) - SizedBox( - height: heightOfViewTypes[index].toDouble(), - child: HtmlElementView( - key: UniqueKey(), viewType: viewtypeIDs[index]), - ), - Divider(), - ], - ); - }, - ), + // child: MarkdownWidget(data: markdown), //hmmm + child: _buildForZooms(level), ) + + // 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) + // SizedBox( + // height: heightOfViewTypes[index].toDouble(), + // child: HtmlElementView( + // key: UniqueKey(), viewType: viewtypeIDs[index]), + // ), + // Divider(), + // ], + // ); + // }, + // ), + // ) ]) : const Center(child: CircularProgressIndicator()); } From de3d7f8bfc9a559e5f1565f842789d216b17c045 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 10 Jun 2025 14:58:34 -0400 Subject: [PATCH 13/64] works, and disabled zoom out button at root, and zoom in, when nodes don't have any children --- lib/collapsableEmailsWeb.dart | 354 +++++++++++++++------------------- 1 file changed, 157 insertions(+), 197 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index b748daf..4dca67b 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -53,17 +53,20 @@ class _CollapsableEmailsState extends State { String markdown = ''; List> sentinel = []; int level = 0; - AugmentTree root = AugmentTree(); + AugmentTree zoomTreeRoot = AugmentTree(); + late AugmentTree currentZoomNode; + bool zoomOut = false; + bool zoomIn = true; @override void initState() { super.initState(); _markdownConverter(); // _registerViewFactory(widget.threadHTML); - // _serializableData(widget.threadIDs); // this + _serializableData(widget.threadIDs); // this _markdown2Tree(markdown); _keyListener(); - _buildForZooms(level); + _buildForZooms(); } @override @@ -111,6 +114,7 @@ class _CollapsableEmailsState extends State { } tree.children = [newNode]; } else { + newNode.parent = tree; tree.children.add(newNode); } // } else{ // so the node should go high @@ -131,12 +135,12 @@ class _CollapsableEmailsState extends State { return; } - print("> ${node2add.tag}"); + // print("> ${node2add.tag}"); _add2Tree(tree.children.last, node2add); } else if ((hirarchyDict[node2add.tag] ?? -1) == (hirarchyDict[tree.children.last.ogTag] ?? -1)) { - print("equals??"); + // print("equals??"); tree.children.add(newNode); newNode.parent = tree; } @@ -145,8 +149,6 @@ class _CollapsableEmailsState extends State { void _markdown2Tree(String text) { print("started markdown2tree"); - int highest = 0; - AugmentTree zoomTreeRoot = AugmentTree(); final List nakedList = md.Document().parseLines(text.split('\n')); List pList = []; List h1List = []; @@ -169,46 +171,23 @@ class _CollapsableEmailsState extends State { } else if (node.tag == 'h2') { // i dont add any since i dont have it, maybe the function makes sense h2List.add(node.textContent); + _add2Tree(zoomTreeRoot, node); // fix this } else if (node.tag == 'h3') { h3List.add(node.textContent); - root.children.add(temp); _add2Tree(zoomTreeRoot, node); } else if (node.tag == 'h4') { h4List.add(node.textContent); //this broke it _add2Tree(zoomTreeRoot, node); // change to temp - if (root.children.isNotEmpty) { - root.children.last.children.add(temp); - } } else if (node.tag == 'h5') { h5List.add(node.textContent); print(node.textContent); _add2Tree(zoomTreeRoot, node); - if (root.children.last.children.isNotEmpty) { - print("h5 index ${root.children.last.children.length}"); - root.children.last.children.last.children.add(temp); - print( - "h5 after index length ${root.children.last.children.last.children.length}"); - } } else if (node.tag == 'h6') { h6List.add(node.textContent); - if (root.children.last.children.isNotEmpty) { - print( - "h6 index ${root.children.last.children.last.children.length}"); - root.children.last.children.last.children.add(temp); - print(node.textContent); - _add2Tree(zoomTreeRoot, node); - } - // root.children.last.children.last.children.add(temp); + _add2Tree(zoomTreeRoot, node); // fix this } else if (node.tag == 'p' || node.tag == 'ul' || node.tag == 'li') { pList.add(node.textContent); _add2Tree(zoomTreeRoot, node); // fix this - - if (root.children.isEmpty) { - root.children.add(temp); - } else if (root.children.last.children.isNotEmpty) { - //perhaps recursive - root.children.last.children.last.children.add(temp); - } } } } @@ -216,31 +195,32 @@ class _CollapsableEmailsState extends State { this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList]; sentinel.removeWhere((hList) => hList.isEmpty); - print('algorithm adding: '); - print("first layer: ${zoomTreeRoot.children}"); - print("first node: ${zoomTreeRoot.children[0].data}"); //good - print("second layer: ${zoomTreeRoot.children.last.children}"); //good - print( - "first node: ${zoomTreeRoot.children.last.children.first.data}"); //good - for (int n = 1; n < zoomTreeRoot.children.last.children.length - 1; n++) { - //good - print(zoomTreeRoot.children.last.children[n].data); - } - print("last node: ${zoomTreeRoot.children.last.children.last.data}"); //good - print("third layer"); - for (int thirdLayer = 0; - thirdLayer < zoomTreeRoot.children.last.children.length; - thirdLayer++) { - print(zoomTreeRoot.children.last.children[thirdLayer].children); - } - print("third layer contents first"); //not sure + // print('algorithm adding: '); + // print("first layer: ${zoomTreeRoot.children}"); + // print("first node: ${zoomTreeRoot.children[0].data}"); //good + // print("second layer: ${zoomTreeRoot.children.last.children}"); //good + // print( + // "first node: ${zoomTreeRoot.children.last.children.first.data}"); //good + // for (int n = 1; n < zoomTreeRoot.children.last.children.length - 1; n++) { + // //good + // print(zoomTreeRoot.children.last.children[n].data); + // } + // print("last node: ${zoomTreeRoot.children.last.children.last.data}"); //good + // print("third layer"); + // for (int thirdLayer = 0; + // thirdLayer < zoomTreeRoot.children.last.children.length; + // thirdLayer++) { + // print(zoomTreeRoot.children.last.children[thirdLayer].children); + // } + // print("third layer contents first"); //not sure - print(zoomTreeRoot.children.last.children[5].children[0].data); //good - for (int contentsOf4 = 1; - contentsOf4 < zoomTreeRoot.children.last.children[5].children.length; - contentsOf4++) { - print(zoomTreeRoot.children.last.children[5].children[contentsOf4].data); - } + // print(zoomTreeRoot.children.last.children[5].children[0].data); //good + // for (int contentsOf4 = 1; + // contentsOf4 < zoomTreeRoot.children.last.children[5].children.length; + // contentsOf4++) { + // print(zoomTreeRoot.children.last.children[5].children[contentsOf4].data); + // } + currentZoomNode = zoomTreeRoot; if (!mounted) return; setState(() { _isLoaded = true; @@ -256,57 +236,67 @@ class _CollapsableEmailsState extends State { setState(() { level = (level - 1).clamp(0, sentinel.length - 1); }); - // _buildForZooms(level + 1); //probably need a level? } else if (keyEvent.key == "b") { print("b"); setState(() { level = (level + 1).clamp(0, sentinel.length - 1); }); - // _buildForZooms(level - 1); //probably need a level? } } - void _registerViewFactory(List 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 _goToChildren(int index) async { + final target = currentZoomNode.children[index]; + if (target.children.isNotEmpty) { + setState(() { + currentZoomNode = target; + }); + } else { + print("This child has no further children."); } + + // if (currentZoomNode.children.isNotEmpty) { + // setState(() { + // zoomIn = true; + // zoomOut = true; + // currentZoomNode = currentZoomNode.children[index]; + // if (currentZoomNode.children[index].children.isEmpty) { + // print('disable in'); + // setState(() { + // zoomIn = false; + // }); + // } + // }); + // } else { + // print("disable zoom down"); + // setState(() { + // zoomIn = false; + // }); + // } + } + + void _goToParent() async { + if (currentZoomNode.parent != null) { + setState(() { + currentZoomNode = currentZoomNode.parent!; + }); + } else { + print("Already at root."); + } + + // print("parent ${currentZoomNode.parent}"); + // print("parent ${currentZoomNode.parent!.parent}"); + // if (currentZoomNode.parent != null) { + // setState(() { + // currentZoomNode = currentZoomNode.parent!; + // if (currentZoomNode.parent == null) { + // setState(() { + // zoomOut = false; + // }); + // } + // }); + // } else if (currentZoomNode.parent == null || + // currentZoomNode.parent!.parent == null) { + // print("disable zoom up"); } void _serializableData(String threadID) async { @@ -361,7 +351,6 @@ class _CollapsableEmailsState extends State { } } else if (keyEvent.key == 'w') { print("you pressed 'w'"); - // getTopLevel(); } } @@ -376,79 +365,56 @@ class _CollapsableEmailsState extends State { web.window.document.addEventListener('keydown', _listener!); } - // void getTopLevel() { - // print("started top"); - // int highest = 0; - // AugmentTree zoomTreeRoot = AugmentTree(); - // // zoomTreeRoot.data = emailsHTML[0]; // whole thing - - // while (highest < hirarchy.length - 1) { - // var highestElement = web.document.querySelectorAll(hirarchy[highest]); - // print("nodelist $highestElement"); - - // if (highestElement.isNull || highestElement.length == 0) { - // //from h1, h2, h3, ..., p. - // highest += 1; - // print(hirarchy[highest]); - // } else { - // AugmentTree newLevel = AugmentTree(); // list of children of each level - // for (int i = 0; i < highestElement.length; i++) { - // print(highestElement.item(i)?.textContent); - - // tagsCollected - // .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); - // newLevel.children - // .add(highestElement.item(i)?.textContent ?? "nameless subtitle"); - // } - // // traverse to last node and add new level to it - // // next - // if (zoomTreeRoot.node == null) { - // zoomTreeRoot.node = newLevel; - // } else { - // AugmentTree temp = zoomTreeRoot; - // while (true) { - // //get to the last and assign node = last node - // if (temp.node != null) { - // temp = temp.node!; - // } else { - // temp.node = newLevel; - // break; - // } - // } - // } - - // highest += 1; - // } - // } - // print("out safely"); - // print(tagsCollected); //instead of a list make a tree - // print(zoomTreeRoot.children); - // print(zoomTreeRoot.node?.children); - // print(zoomTreeRoot.node?.node?.children); - // print(zoomTreeRoot.node?.node?.node?.children); - // } - - Widget _buildForZooms(int lvl) { - this.level = lvl; - if (lvl < 0 || lvl >= sentinel.length) { - return Center(child: Text("No content at level $lvl")); + Widget _buildForZooms({Key? key}) { + if (!_isLoaded) { + return const Center(child: CircularProgressIndicator()); // loading screen } + final canZoomOut = currentZoomNode.parent != null; + return ListView.builder( - itemCount: this.sentinel[level].length, + key: key, + itemCount: currentZoomNode.children.length, itemBuilder: (context, index) { + final childNode = currentZoomNode.children[index]; + final canZoomIn = childNode.children.isNotEmpty; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), child: Material( elevation: 1, borderRadius: BorderRadius.circular(12), color: Theme.of(context).colorScheme.surface, - surfaceTintColor: Theme.of(context).colorScheme.surfaceTint, + surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, child: Padding( padding: const EdgeInsets.all(16.0), - child: MarkdownBlock( - data: sentinel[level][index], // one string of markdown - config: MarkdownConfig - .darkConfig, // or lightConfig depending on theme + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 4.0, + children: [ + OutlinedButton( + onPressed: + canZoomOut ? () => _goToParent() : null, + child: Icon(Icons.north_west_sharp), + ), + OutlinedButton( + onPressed: + canZoomIn ? () => _goToChildren(index) : null, + child: Icon(Icons.south_east_sharp), + ), + ], + ), + SizedBox(width: 12.0), + Expanded( + child: MarkdownBlock( + data: currentZoomNode + .children[index].data, // one string of markdown + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + ], ), ), ), @@ -462,43 +428,37 @@ class _CollapsableEmailsState extends State { return _isLoaded ? Column(children: [ Expanded( - // child: MarkdownWidget(data: markdown), //hmmm - child: _buildForZooms(level), + 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) + SizedBox( + height: 800, + child: + _buildForZooms(key: ValueKey(currentZoomNode))), + Divider(), + ], + ); + }, + ), ) - - // 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) - // SizedBox( - // height: heightOfViewTypes[index].toDouble(), - // child: HtmlElementView( - // key: UniqueKey(), viewType: viewtypeIDs[index]), - // ), - // Divider(), - // ], - // ); - // }, - // ), - // ) ]) : const Center(child: CircularProgressIndicator()); } From 69b5408f73259dbca3aac2578046674307eb25c2 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 10 Jun 2025 17:26:31 -0400 Subject: [PATCH 14/64] dynamicish sizing and cleanup --- lib/collapsableEmailsWeb.dart | 99 ++++++----------------------------- 1 file changed, 15 insertions(+), 84 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 4dca67b..b070d6a 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,9 +1,6 @@ -import 'dart:collection'; import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; 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'; import 'package:html2md/html2md.dart' as html2md; @@ -117,13 +114,6 @@ class _CollapsableEmailsState extends State { newNode.parent = tree; tree.children.add(newNode); } - // } else{ // so the node should go high - // _add2Tree(tree.children.last, node2add); - // } - - //maybe? - // } else { - // } } 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)) { @@ -135,8 +125,6 @@ class _CollapsableEmailsState extends State { return; } - // print("> ${node2add.tag}"); - _add2Tree(tree.children.last, node2add); } else if ((hirarchyDict[node2add.tag] ?? -1) == (hirarchyDict[tree.children.last.ogTag] ?? -1)) { @@ -194,32 +182,6 @@ class _CollapsableEmailsState extends State { this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList]; sentinel.removeWhere((hList) => hList.isEmpty); - - // print('algorithm adding: '); - // print("first layer: ${zoomTreeRoot.children}"); - // print("first node: ${zoomTreeRoot.children[0].data}"); //good - // print("second layer: ${zoomTreeRoot.children.last.children}"); //good - // print( - // "first node: ${zoomTreeRoot.children.last.children.first.data}"); //good - // for (int n = 1; n < zoomTreeRoot.children.last.children.length - 1; n++) { - // //good - // print(zoomTreeRoot.children.last.children[n].data); - // } - // print("last node: ${zoomTreeRoot.children.last.children.last.data}"); //good - // print("third layer"); - // for (int thirdLayer = 0; - // thirdLayer < zoomTreeRoot.children.last.children.length; - // thirdLayer++) { - // print(zoomTreeRoot.children.last.children[thirdLayer].children); - // } - // print("third layer contents first"); //not sure - - // print(zoomTreeRoot.children.last.children[5].children[0].data); //good - // for (int contentsOf4 = 1; - // contentsOf4 < zoomTreeRoot.children.last.children[5].children.length; - // contentsOf4++) { - // print(zoomTreeRoot.children.last.children[5].children[contentsOf4].data); - // } currentZoomNode = zoomTreeRoot; if (!mounted) return; setState(() { @@ -253,50 +215,16 @@ class _CollapsableEmailsState extends State { } else { print("This child has no further children."); } - - // if (currentZoomNode.children.isNotEmpty) { - // setState(() { - // zoomIn = true; - // zoomOut = true; - // currentZoomNode = currentZoomNode.children[index]; - // if (currentZoomNode.children[index].children.isEmpty) { - // print('disable in'); - // setState(() { - // zoomIn = false; - // }); - // } - // }); - // } else { - // print("disable zoom down"); - // setState(() { - // zoomIn = false; - // }); - // } } void _goToParent() async { - if (currentZoomNode.parent != null) { - setState(() { - currentZoomNode = currentZoomNode.parent!; - }); - } else { - print("Already at root."); - } - - // print("parent ${currentZoomNode.parent}"); - // print("parent ${currentZoomNode.parent!.parent}"); - // if (currentZoomNode.parent != null) { - // setState(() { - // currentZoomNode = currentZoomNode.parent!; - // if (currentZoomNode.parent == null) { - // setState(() { - // zoomOut = false; - // }); - // } - // }); - // } else if (currentZoomNode.parent == null || - // currentZoomNode.parent!.parent == null) { - // print("disable zoom up"); + if (currentZoomNode.parent != null) { + setState(() { + currentZoomNode = currentZoomNode.parent!; + }); + } else { + print("Already at root."); + } } void _serializableData(String threadID) async { @@ -449,16 +377,19 @@ class _CollapsableEmailsState extends State { }, ), if (isExpanded) - SizedBox( - height: 800, - child: - _buildForZooms(key: ValueKey(currentZoomNode))), + ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100, + maxHeight: MediaQuery.of(context).size.height * 0.6, + ), + child: _buildForZooms(key: ValueKey(currentZoomNode)), + ), Divider(), ], ); }, ), - ) + ), ]) : const Center(child: CircularProgressIndicator()); } From 160fe25be388aee07eb70ef96e5d502687128d80 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 11 Jun 2025 15:12:58 -0400 Subject: [PATCH 15/64] works, and optimized time for checking the node type --- lib/collapsableEmailsAndroid.dart | 291 ++++++++++++++++++++++++++++-- 1 file changed, 276 insertions(+), 15 deletions(-) diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart index f2cdf01..64f2cc8 100644 --- a/lib/collapsableEmailsAndroid.dart +++ b/lib/collapsableEmailsAndroid.dart @@ -1,6 +1,9 @@ -import 'structs.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_widget_from_html/flutter_widget_from_html.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 thread; // email id's in the form xyz@gmail.com @@ -20,29 +23,287 @@ class _CollapsableEmailsState extends State { List emailsHTML = []; //html of the emails in the thread // build attachments with the forldar name and id Set _expandedEmails = {}; //open emails - List viewtypeIDs = []; //IDs of the viewtypes, order matters - List heightOfViewTypes = []; //the height of each viewtype List emailsInThread = []; bool _isLoaded = false; + List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; + Map hirarchyDict = { + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4, + "h5": 6, + "h6": 7, + "p": 8, + "ul": 8, + "li": 8, + }; + + List tagsCollected = []; + String markdown = ''; + List> sentinel = []; + int level = 0; + AugmentTree zoomTreeRoot = AugmentTree(); + late AugmentTree currentZoomNode; + bool zoomOut = false; + bool zoomIn = true; + @override void initState() { super.initState(); - //html + _markdownConverter(); + _serializableData(widget.threadIDs); // this + _markdown2Tree(markdown); + _buildForZooms(); } + @override + void dispose() { + super.dispose(); + } + + void _markdownConverter() async { + markdown = html2md.convert(widget.threadHTML[0]); + } + + 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(String text) { + print("started markdown2tree"); + final List nakedList = md.Document().parseLines(text.split('\n')); + + for (var node in nakedList) { + //maybe do an add function, but isn't this it? + if (node is md.Element) { + // print(node.textContent); + AugmentTree temp = AugmentTree(); + temp.data = node.textContent; + temp.ogTag = node.tag; + if (hirarchyDict.containsKey(node.tag)) { + _add2Tree(zoomTreeRoot, node); + } + } + + currentZoomNode = zoomTreeRoot; + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + } + + void _goToChildren(int index) async { + final target = currentZoomNode.children[index]; + if (target.children.isNotEmpty) { + setState(() { + currentZoomNode = target; + }); + } else { + print("This child has no further children."); + } + + // if (currentZoomNode.children.isNotEmpty) { + // setState(() { + // zoomIn = true; + // zoomOut = true; + // currentZoomNode = currentZoomNode.children[index]; + // if (currentZoomNode.children[index].children.isEmpty) { + // print('disable in'); + // setState(() { + // zoomIn = false; + // }); + // } + // }); + // } else { + // print("disable zoom down"); + // setState(() { + // zoomIn = false; + // }); + // } + } + + void _goToParent() async { + if (currentZoomNode.parent != null) { + setState(() { + currentZoomNode = currentZoomNode.parent!; + }); + } else { + print("Already at root."); + } + + // print("parent ${currentZoomNode.parent}"); + // print("parent ${currentZoomNode.parent!.parent}"); + // if (currentZoomNode.parent != null) { + // setState(() { + // currentZoomNode = currentZoomNode.parent!; + // if (currentZoomNode.parent == null) { + // setState(() { + // zoomOut = false; + // }); + // } + // }); + // } else if (currentZoomNode.parent == null || + // currentZoomNode.parent!.parent == null) { + // print("disable zoom up"); + } + + void _serializableData(String threadID) async { + emailsInThread = await ApiService().threadsInSerializable(threadID); + print("done thread serializable"); + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + Widget _buildForZooms({Key? key}) { + if (!_isLoaded) { + return const Center(child: CircularProgressIndicator()); // loading screen + } + final canZoomOut = currentZoomNode.parent != null; + + return ListView.builder( + key: key, + itemCount: currentZoomNode.children.length, + itemBuilder: (context, index) { + final childNode = currentZoomNode.children[index]; + final canZoomIn = childNode.children.isNotEmpty; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), + child: Material( + elevation: 1, + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 4.0, + children: [ + OutlinedButton( + onPressed: canZoomOut ? () => _goToParent() : null, + child: Icon(Icons.north_west_sharp), + ), + OutlinedButton( + onPressed: + canZoomIn ? () => _goToChildren(index) : null, + child: Icon(Icons.south_east_sharp), + ), + ], + ), + SizedBox(width: 12.0), + Expanded( + child: MarkdownBlock( + data: currentZoomNode + .children[index].data, // one string of markdown + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + ], + ), + ), + ), + ); + }, + ); + } @override Widget build(BuildContext context) { - return Scaffold( - body: ListView( - children: [ - HtmlWidget( - widget.threadHTML[0], - // renderMode: RenderMode.listView, - ) - ] - ) - ); + 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(key: ValueKey(currentZoomNode))), + Divider(), + ], + ); + }, + ), + ), + ]) + : const Center(child: CircularProgressIndicator()); } } From 654520ad3a3fdfe7f0a9129493987170f0322dd2 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 11 Jun 2025 20:36:15 -0400 Subject: [PATCH 16/64] cleaned and called numbering per tree when its done --- lib/collapsableEmailsWeb.dart | 234 +++++++++++----------------------- 1 file changed, 75 insertions(+), 159 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index b070d6a..0401130 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,5 +1,3 @@ -import 'dart:js_interop'; -import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; import 'api_service.dart'; import 'structs.dart'; @@ -25,59 +23,53 @@ class _CollapsableEmailsState extends State { List emailsHTML = []; //html of the emails in the thread // build attachments with the forldar name and id Set _expandedEmails = {}; //open emails - List viewtypeIDs = []; //IDs of the viewtypes, order matters - List heightOfViewTypes = []; //the height of each viewtype + List emailsInThread = []; bool _isLoaded = false; - static bool _isListenerRegistered = false; - static bool left = true; - static bool right = true; - web.EventListener? _listener; List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; Map hirarchyDict = { "h1": 1, "h2": 2, "h3": 3, "h4": 4, - "h5": 6, - "h6": 7, + "h5": 5, + "h6": 6, "p": 8, "ul": 8, "li": 8, }; List tagsCollected = []; - String markdown = ''; + List allMarkdown = []; List> sentinel = []; int level = 0; AugmentTree zoomTreeRoot = AugmentTree(); - late AugmentTree currentZoomNode; + // late AugmentTree currentZoomNode; + late List currentZoomTree = []; bool zoomOut = false; bool zoomIn = true; + late List threadNodes = []; @override void initState() { super.initState(); + threadNodes = []; + currentZoomTree = []; _markdownConverter(); - // _registerViewFactory(widget.threadHTML); _serializableData(widget.threadIDs); // this - _markdown2Tree(markdown); - _keyListener(); - _buildForZooms(); + _markdown2Tree(allMarkdown); } @override void dispose() { - if (_listener != null) { - web.window.document.removeEventListener('keydown', _listener!); - _listener = null; - _isListenerRegistered = false; - } super.dispose(); } void _markdownConverter() async { - markdown = html2md.convert(widget.threadHTML[0]); + for (int email = 0; email < widget.threadHTML.length; email++) { + String markdown = html2md.convert(widget.threadHTML[email]); + allMarkdown.add(markdown); + } } void _add2Tree(AugmentTree tree, md.Element node2add) { @@ -89,7 +81,6 @@ class _CollapsableEmailsState extends State { //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 - print('is empty'); tree.children.add(newNode); newNode.parent = tree; } else if (tree.children.isNotEmpty && @@ -128,99 +119,69 @@ class _CollapsableEmailsState extends State { _add2Tree(tree.children.last, node2add); } else if ((hirarchyDict[node2add.tag] ?? -1) == (hirarchyDict[tree.children.last.ogTag] ?? -1)) { - // print("equals??"); tree.children.add(newNode); newNode.parent = tree; } } } - void _markdown2Tree(String text) { + void _markdown2Tree(List text) { print("started markdown2tree"); - final List nakedList = md.Document().parseLines(text.split('\n')); - List pList = []; - List h1List = []; - List h2List = []; - List h3List = []; - List h4List = []; - List h5List = []; - List h6List = []; - - for (var node in nakedList) { - //maybe do an add function, but isn't this it? - if (node is md.Element) { - // print(node.textContent); - AugmentTree temp = AugmentTree(); - temp.data = node.textContent; - temp.ogTag = node.tag; - if (node.tag == 'h1') { - h1List.add(node.textContent); - _add2Tree(zoomTreeRoot, node); - } else if (node.tag == 'h2') { - // i dont add any since i dont have it, maybe the function makes sense - h2List.add(node.textContent); - _add2Tree(zoomTreeRoot, node); // fix this - } else if (node.tag == 'h3') { - h3List.add(node.textContent); - _add2Tree(zoomTreeRoot, node); - } else if (node.tag == 'h4') { - h4List.add(node.textContent); //this broke it - _add2Tree(zoomTreeRoot, node); // change to temp - } else if (node.tag == 'h5') { - h5List.add(node.textContent); - print(node.textContent); - _add2Tree(zoomTreeRoot, node); - } else if (node.tag == 'h6') { - h6List.add(node.textContent); - _add2Tree(zoomTreeRoot, node); // fix this - } else if (node.tag == 'p' || node.tag == 'ul' || node.tag == 'li') { - pList.add(node.textContent); - _add2Tree(zoomTreeRoot, node); // fix this + for (int emailsMD = 0; emailsMD < text.length; emailsMD++) { + final List 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); } - this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList]; - sentinel.removeWhere((hList) => hList.isEmpty); - currentZoomNode = zoomTreeRoot; if (!mounted) return; setState(() { _isLoaded = true; }); } - void handleKeyDownMD(web.Event event) async { - final keyEvent = event as web.KeyboardEvent; - - if (!mounted) return; - if (keyEvent.key == 'a') { - print("key a"); - setState(() { - level = (level - 1).clamp(0, sentinel.length - 1); - }); - } else if (keyEvent.key == "b") { - print("b"); - setState(() { - level = (level + 1).clamp(0, sentinel.length - 1); - }); - } - } - - void _goToChildren(int index) async { - final target = currentZoomNode.children[index]; + void _goToChildren(int indexThread, int index) async { + final target = currentZoomTree[indexThread].children[index]; if (target.children.isNotEmpty) { setState(() { - currentZoomNode = target; + currentZoomTree[indexThread] = target; }); } else { print("This child has no further children."); } } - void _goToParent() async { - if (currentZoomNode.parent != null) { + void _goToParent(int indexThread) async { + if (currentZoomTree[indexThread].parent != null) { setState(() { - currentZoomNode = currentZoomNode.parent!; + currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!; }); } else { print("Already at root."); @@ -230,82 +191,30 @@ class _CollapsableEmailsState extends State { void _serializableData(String threadID) async { emailsInThread = await ApiService().threadsInSerializable(threadID); print("done thread serializable"); + if (!mounted) return; setState(() { _isLoaded = true; }); } - void handleKeyDownHTML(web.Event event) { - final keyEvent = event as web.KeyboardEvent; - - if (keyEvent.key == 'G') { - print('You pressed the "G" key!'); - final rightPurpleNums = web.document.getElementsByClassName("right"); - _CollapsableEmailsState.right = !_CollapsableEmailsState.right; - final newOpacity = _CollapsableEmailsState.right ? '1.0' : '0.0'; - for (int i = 0; i < rightPurpleNums.length; i++) { - final currentElement = rightPurpleNums.item(i) as web.HTMLElement; - currentElement.style.opacity = newOpacity; - } - } else if (keyEvent.key == 'H') { - print('You pressed the "H" key!'); - final leftPurpleNums = web.document.getElementsByClassName("left"); - _CollapsableEmailsState.left = !_CollapsableEmailsState.left; - final newOpacity = _CollapsableEmailsState.left ? '1.0' : '0.0'; - for (int i = 0; i < leftPurpleNums.length; i++) { - final currentElement = leftPurpleNums.item(i) as web.HTMLElement; - currentElement.style.opacity = newOpacity; - } - } else if (keyEvent.key == 'm') { - print("you pressed 'm'"); - final purpleNums = web.document.getElementsByClassName("purplenumber"); - _CollapsableEmailsState.left = true; - _CollapsableEmailsState.right = true; - - for (int i = 0; i < purpleNums.length; i++) { - final currentElement = purpleNums.item(i) as web.HTMLElement; - currentElement.style.opacity = '1.0'; - } - } else if (keyEvent.key == 'n') { - print("you pressed 'n'"); - final purpleNums = web.document.getElementsByClassName("purplenumber"); - _CollapsableEmailsState.left = false; - _CollapsableEmailsState.right = false; - - for (int i = 0; i < purpleNums.length; i++) { - final currentElement = purpleNums.item(i) as web.HTMLElement; - currentElement.style.opacity = '0.0'; - } - } else if (keyEvent.key == 'w') { - print("you pressed 'w'"); - } - } - - void _keyListener() { - if (_isListenerRegistered) return; - _isListenerRegistered = true; - - // Convert the top-level function to JS-compatible - // _listener = handleKeyDownHTML.toJS; - _listener = handleKeyDownMD.toJS; - - web.window.document.addEventListener('keydown', _listener!); - } - - Widget _buildForZooms({Key? key}) { + Widget _buildForZooms(int indexThread) { + // IF I GIVE IT THE INDEX???? if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } - final canZoomOut = currentZoomNode.parent != null; + + final AugmentTree currentZoomNodeForThisEmail = + currentZoomTree[indexThread]; + + final canZoomOut = currentZoomNodeForThisEmail.parent != null; return ListView.builder( - key: key, - itemCount: currentZoomNode.children.length, + itemCount: currentZoomNodeForThisEmail.children.length, itemBuilder: (context, index) { - final childNode = currentZoomNode.children[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( @@ -323,12 +232,13 @@ class _CollapsableEmailsState extends State { children: [ OutlinedButton( onPressed: - canZoomOut ? () => _goToParent() : null, + canZoomOut ? () => _goToParent(indexThread) : null, child: Icon(Icons.north_west_sharp), ), OutlinedButton( - onPressed: - canZoomIn ? () => _goToChildren(index) : null, + onPressed: canZoomIn + ? () => _goToChildren(indexThread, index) + : null, child: Icon(Icons.south_east_sharp), ), ], @@ -336,12 +246,17 @@ class _CollapsableEmailsState extends State { SizedBox(width: 12.0), Expanded( child: MarkdownBlock( - data: currentZoomNode - .children[index].data, // one string of markdown + data: childNode.data, + // data: currentZoomNode + // .children[index].data, // one string of markdown config: MarkdownConfig .darkConfig, // or lightConfig depending on theme ), ), + Text( + childNode.numbering, + style: TextStyle(color: Color(Colors.purple[400]!.value)), + ) ], ), ), @@ -382,7 +297,8 @@ class _CollapsableEmailsState extends State { minHeight: 100, maxHeight: MediaQuery.of(context).size.height * 0.6, ), - child: _buildForZooms(key: ValueKey(currentZoomNode)), + child: _buildForZooms(index), //show the tree + // child: _buildForZooms(key: ValueKey(currentZoomNode)), ), Divider(), ], From 8568eafba33ddb15de10c6315acd5ecb6efeeaac Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 11 Jun 2025 20:37:09 -0400 Subject: [PATCH 17/64] struct for markdown, with a numbering algorithm for each node that goes in the form of #a#b#c --- lib/structs.dart | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/structs.dart b/lib/structs.dart index 4672792..b9dbad3 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -1,6 +1,7 @@ //data structures import 'dart:typed_data'; +import 'package:markdown/markdown.dart' as md; class GetThreadResponse { final int id; @@ -151,6 +152,48 @@ class AttachmentResponse { } class AugmentTree { - List children = []; - AugmentTree? node; + List 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); + } + } } From 69ed5eb6ab5d4108c6e42ace5891d2aa3d5eebb7 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 12 Jun 2025 01:01:01 -0400 Subject: [PATCH 18/64] handle jumping --- lib/collapsableEmailsWeb.dart | 69 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 0401130..c58d907 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -9,11 +9,14 @@ class CollapsableEmails extends StatefulWidget { final List thread; // email id's in the form xyz@gmail.com final List threadHTML; final String threadIDs; + final String? targetJumpNumbering; - CollapsableEmails( - {required this.thread, - required this.threadHTML, - required this.threadIDs}); + const CollapsableEmails({ + required this.thread, + required this.threadHTML, + required this.threadIDs, + this.targetJumpNumbering, + }); @override State createState() => _CollapsableEmailsState(); @@ -60,6 +63,16 @@ class _CollapsableEmailsState extends State { _markdown2Tree(allMarkdown); } + @override + void didUpdateWidget(covariant CollapsableEmails oldWidget) { + // TODO: implement didUpdateWidget + super.didUpdateWidget(oldWidget); + if (widget.targetJumpNumbering != null && + widget.targetJumpNumbering != oldWidget.targetJumpNumbering) { + _handleJump(widget.targetJumpNumbering!); + } + } + @override void dispose() { super.dispose(); @@ -266,6 +279,54 @@ class _CollapsableEmailsState extends State { ); } + 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.')), + ); + } + } + + // Placeholder for your actual node finding logic within AugmentTree + AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) { + // Implement your tree traversal here (e.g., DFS or BFS) + // to find the AugmentTree node corresponding to the `numbering`. + // This is a simplified example: + if (root.numbering == numbering) { + // Assuming 'id' can be your 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 From 34989d821393d2d01e8d292fa34875459a9176bb Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 12 Jun 2025 01:01:31 -0400 Subject: [PATCH 19/64] cleaned, and added the new paremeter for jumping --- lib/emailViewWeb.dart | 128 ++++-------------------------------------- 1 file changed, 11 insertions(+), 117 deletions(-) diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index 08551eb..ace3e7f 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -2,7 +2,6 @@ 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'; @@ -44,6 +43,7 @@ class _EmailViewState extends State { {'id': 'marker2', 'x': 150, 'y': 200}, {'id': 'marker3', 'x': 250, 'y': 300}, ]; + String? _targetJumpNumbering; @override void initState() { @@ -55,39 +55,19 @@ class _EmailViewState extends State { // _registerViewFactory(currentContent); } - // void _registerViewFactory(List 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); } + void _handleJumpRequest(String numbering) { + setState(() { + _targetJumpNumbering = numbering; + }); + } // 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( @@ -98,28 +78,10 @@ class _EmailViewState extends State { Column( children: [ EmailToolbar( - onJumpToSpan: _scrollToNumber, - onButtonPressed: () => {}, - // AugmentClasses.handleJump(viewTypeId, '1'); - // print("button got pressed?"); - - // _registerViewFactory(r""" - //

Welcome to My Website

- //

This is a simple HTML page.

- //

What is HTML?

- //

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).

- //

Here's a simple list:

- //
    - //
  • HTML elements are the building blocks of HTML pages
  • - //
  • HTML uses tags like <tag> to organize and format content
  • - //
  • CSS is used with HTML to style pages
  • - //
- //

Copyright © 2023

- // """); - // print("change"); - // widget.emailContent = r" - - // + onJumpToSpan: _handleJumpRequest, + onButtonPressed: () => { + print("email tool bar pressed") + }, ), Row( // title of email @@ -162,79 +124,11 @@ class _EmailViewState extends State { thread: widget.messages, //this wont work in serializable threadHTML: widget.emailContent, threadIDs: widget.id, + targetJumpNumbering: _targetJumpNumbering, ), ), - // Expanded( - // child: HtmlElementView( - // key: UniqueKey(), - // viewType: viewTypeId, - // ), - // ), ], ), - - // Overlay widgets dynamically based on marker positions - // FutureBuilder>>( - // 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), - // ), - // ), - // ), - // ), - // ), ], )); } From edec45669dd4665922e19828bff8a9b94352f4c7 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 12 Jun 2025 16:07:31 -0400 Subject: [PATCH 20/64] viewspecs and jump textfield controller --- lib/augment.dart | 65 +++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 509b3b0..2b8c5de 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,16 +1,18 @@ import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/attachmentDownload.dart'; +import 'package:crab_ui/collapsableEmails.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'package: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 @@ -19,7 +21,8 @@ class EmailToolbar extends StatefulWidget { class _DynamicClassesAugment extends State { String selectedClass = 'Class 1'; - // TextEditingController _jumpController = TextEditingController(); + TextEditingController _jumpController = TextEditingController(); + TextEditingController _viewspecsController = TextEditingController(); // late final FocusNode _JumpItemfocusNode; // late final FocusNode _viewSpecsfocusNode; @@ -46,7 +49,7 @@ class _DynamicClassesAugment extends State { void dispose() { // _JumpItemfocusNode.dispose(); // _viewSpecsfocusNode.dispose(); - // _jumpController.dispose(); + _jumpController.dispose(); super.dispose(); } @@ -71,20 +74,20 @@ class _DynamicClassesAugment extends State { 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); @@ -131,10 +134,10 @@ class _DynamicClassesAugment extends State { // width: 8, // ), Container( - width: 50, + width: 100, height: 30, child: TextField( - // controller: _jumpController, + controller: _jumpController, decoration: InputDecoration( border: OutlineInputBorder(), // suffixIcon: Icon(Icons.search) @@ -142,7 +145,7 @@ class _DynamicClassesAugment extends State { onSubmitted: (value) { print("onSubmitted"); if (value.isNotEmpty) { - widget.onJumpToSpan(value); + widget.onJumpToNumbering(value); } }, ), @@ -179,14 +182,18 @@ class _DynamicClassesAugment extends State { 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( @@ -484,30 +491,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) {} From 361a3add3914a4af7e7310b32a7bda1181cfb192 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 12 Jun 2025 16:08:19 -0400 Subject: [PATCH 21/64] viewspecs handling callback function added --- lib/emailViewWeb.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index ace3e7f..df50e6d 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -5,7 +5,6 @@ import 'augment.dart'; import 'collapsableEmails.dart'; import 'api_service.dart'; - class EmailView extends StatefulWidget { final List emailContent; final String from; @@ -44,6 +43,7 @@ class _EmailViewState extends State { {'id': 'marker3', 'x': 250, 'y': 300}, ]; String? _targetJumpNumbering; + String? _targetViewspecs; @override void initState() { @@ -58,12 +58,19 @@ class _EmailViewState extends State { 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 @@ -78,10 +85,9 @@ class _EmailViewState extends State { Column( children: [ EmailToolbar( - onJumpToSpan: _handleJumpRequest, - onButtonPressed: () => { - print("email tool bar pressed") - }, + onJumpToNumbering: _handleJumpRequest, + onViewspecs: _handleViewspecsRequest, + onButtonPressed: () => {print("email tool bar pressed")}, ), Row( // title of email @@ -121,10 +127,11 @@ class _EmailViewState extends State { Expanded( child: CollapsableEmails( //change here - thread: widget.messages, //this wont work in serializable + thread: widget.messages, //this wont work in serializable threadHTML: widget.emailContent, threadIDs: widget.id, targetJumpNumbering: _targetJumpNumbering, + targetViewspecs: _targetViewspecs, ), ), ], From eadc39c8cf7263c3a6fa00fbe8263f3a80660365 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 12 Jun 2025 16:09:04 -0400 Subject: [PATCH 22/64] viewspecs left, and right numbering visibility --- lib/collapsableEmailsWeb.dart | 86 +++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index c58d907..7422d9e 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -10,12 +10,14 @@ class CollapsableEmails extends StatefulWidget { final List threadHTML; final String threadIDs; final String? targetJumpNumbering; + final String? targetViewspecs; const CollapsableEmails({ required this.thread, required this.threadHTML, required this.threadIDs, this.targetJumpNumbering, + this.targetViewspecs, }); @override @@ -52,6 +54,9 @@ class _CollapsableEmailsState extends State { bool zoomOut = false; bool zoomIn = true; late List threadNodes = []; + static bool leftNumbering = true; + static bool rightNumbering = true; + bool showWhole = false; @override void initState() { @@ -71,6 +76,10 @@ class _CollapsableEmailsState extends State { widget.targetJumpNumbering != oldWidget.targetJumpNumbering) { _handleJump(widget.targetJumpNumbering!); } + if (widget.targetViewspecs != null && + widget.targetViewspecs != oldWidget.targetViewspecs) { + _handleViewspecs(widget.targetViewspecs!); + } } @override @@ -257,6 +266,15 @@ class _CollapsableEmailsState extends State { ], ), 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, @@ -266,10 +284,15 @@ class _CollapsableEmailsState extends State { .darkConfig, // or lightConfig depending on theme ), ), - Text( - childNode.numbering, - style: TextStyle(color: Color(Colors.purple[400]!.value)), - ) + if (rightNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), ], ), ), @@ -309,13 +332,60 @@ class _CollapsableEmailsState extends State { } } - // Placeholder for your actual node finding logic within AugmentTree + 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.contrains('w')) { + setState(() { + showWhole = true; + }); + } + + // else { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')), + // ); + // } + } + AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) { - // Implement your tree traversal here (e.g., DFS or BFS) + //recursively finds the node you mentioned // to find the AugmentTree node corresponding to the `numbering`. - // This is a simplified example: if (root.numbering == numbering) { - // Assuming 'id' can be your numbering return root; } for (var child in root.children) { From e26146ead2e1da6066c3558fad03c458db8b3001 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 18 Jun 2025 16:32:49 -0400 Subject: [PATCH 23/64] clean and fetchMarkdown content added --- lib/api_service.dart | 114 +++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 76 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index 4769377..ecd5a83 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -1,16 +1,13 @@ // 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 'structs.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; - class ApiService { static String ip = ""; static String port = ""; @@ -141,7 +138,6 @@ class ApiService { } catch (e) { print('_getEmailContent caught error: $e'); } - // return content; return HTMLofThread; } @@ -344,75 +340,41 @@ class ApiService { return AttachmentResponse(name: "error", data: Uint8List(0)); } - //TODO: MOVE THIS INTO WEB -// Future>> 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> fetchMarkdownContent( + List IDsString, String emailFolder) async { + List 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 { -// // 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 parsedResult = jsonDecode(result); -// var positions = List>.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 []; -// } -} \ No newline at end of file + 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 json = jsonDecode(response.body); + + MDofThread.add(json['md'] ?? ''); + try { + List 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'); + } + + return MDofThread; + } +} From 1fb4cfd64c75c893f45bc6d439d803cf3befcef1 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 18 Jun 2025 16:35:16 -0400 Subject: [PATCH 24/64] modified for using the markdown --- lib/collapsableEmailsStub.dart | 6 +++--- lib/collapsableEmailsWeb.dart | 29 ++++++++++++++++++----------- lib/email.dart | 3 ++- lib/emailViewWeb.dart | 3 ++- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart index 7c89a88..dd9168f 100644 --- a/lib/collapsableEmailsStub.dart +++ b/lib/collapsableEmailsStub.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart'; class CollapsableEmails extends StatefulWidget { final List thread; // email id's in the form xyz@gmail.com - final List threadHTML; + final List threadMarkdown; final String threadIDs; CollapsableEmails( {required this.thread, - required this.threadHTML, - required this.threadIDs}); + required this.threadMarkdown, + required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs}); @override State createState() => _CollapsableEmailsState(); diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 7422d9e..1183259 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,3 +1,4 @@ +import 'package:english_words/english_words.dart'; import 'package:flutter/material.dart'; import 'api_service.dart'; import 'structs.dart'; @@ -7,14 +8,16 @@ import 'package:markdown/markdown.dart' as md; class CollapsableEmails extends StatefulWidget { final List thread; // email id's in the form xyz@gmail.com - final List threadHTML; + // final List threadHTML; to be replaced with the MD + final List threadMarkdown; final String threadIDs; final String? targetJumpNumbering; final String? targetViewspecs; const CollapsableEmails({ required this.thread, - required this.threadHTML, + // required this.threadHTML, + required this.threadMarkdown, required this.threadIDs, this.targetJumpNumbering, this.targetViewspecs, @@ -63,9 +66,9 @@ class _CollapsableEmailsState extends State { super.initState(); threadNodes = []; currentZoomTree = []; - _markdownConverter(); + // _markdownConverter(); _serializableData(widget.threadIDs); // this - _markdown2Tree(allMarkdown); + _markdown2Tree(widget.threadMarkdown); } @override @@ -87,12 +90,16 @@ class _CollapsableEmailsState extends State { super.dispose(); } - void _markdownConverter() async { - for (int email = 0; email < widget.threadHTML.length; email++) { - String markdown = html2md.convert(widget.threadHTML[email]); - allMarkdown.add(markdown); - } - } + // 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 @@ -369,7 +376,7 @@ class _CollapsableEmailsState extends State { rightNumbering = !rightNumbering; }); } - if (viewspecsQuery.contrains('w')) { + if (viewspecsQuery.contains('w')) { setState(() { showWhole = true; }); diff --git a/lib/email.dart b/lib/email.dart index ff4d600..0b1ded3 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -140,7 +140,8 @@ class EmailPageState extends State { return Scaffold( body: EmailListScreen( emails: emails, - getEmailContent: apiService.fetchEmailContent, + // getEmailContent: apiService.fetchEmailContent, + getEmailContent: apiService.fetchMarkdownContent, folder: widget.selectedFolder, //try to grab from it directly ), ); diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index df50e6d..ceb4ef1 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -128,7 +128,8 @@ class _EmailViewState extends State { child: CollapsableEmails( //change here thread: widget.messages, //this wont work in serializable - threadHTML: widget.emailContent, + // threadHTML: widget.emailContent, // old html + threadMarkdown: widget.emailContent, threadIDs: widget.id, targetJumpNumbering: _targetJumpNumbering, targetViewspecs: _targetViewspecs, From c1afc8875eea14d51f474d38dae547eb99d51202 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 18 Jun 2025 17:12:52 -0400 Subject: [PATCH 25/64] dump --- lib/SonicEmailViewWeb.dart | 7 ++++++- lib/emailViewAndroid.dart | 15 +++++++++++++++ lib/structs.dart | 15 +++++++++++++-- pubspec.yaml | 3 +++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/SonicEmailViewWeb.dart b/lib/SonicEmailViewWeb.dart index 3ad8f76..b5b127a 100644 --- a/lib/SonicEmailViewWeb.dart +++ b/lib/SonicEmailViewWeb.dart @@ -23,6 +23,9 @@ class _SonicEmailViewState extends State { void _scrollToNumber(String spanId) { AugmentClasses.handleJump(spanId); } + void _handleViewspecs(String queryViewspecs) { + return; + } @override void initState() { @@ -90,7 +93,9 @@ class _SonicEmailViewState extends State { children: [ EmailToolbar( onButtonPressed: () => {}, - onJumpToSpan: _scrollToNumber), + onJumpToNumbering: _scrollToNumber, + onViewspecs: _handleViewspecs + ), Row( // title of email children: [ diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart index d6d6981..33eedd3 100644 --- a/lib/emailViewAndroid.dart +++ b/lib/emailViewAndroid.dart @@ -1,3 +1,4 @@ +import 'package:crab_ui/augment.dart'; import 'package:crab_ui/collapsableEmailsAndroid.dart'; import 'package:flutter/material.dart'; // import 'dart:ui_web' as ui; @@ -33,6 +34,16 @@ class EmailView extends StatefulWidget { } class _EmailViewState extends State { + + @override + void initState() { + super.initState(); + } + + void _scrollToNumber(String spanId) { + // AugmentClasses.handleJump(spanId); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -43,6 +54,10 @@ class _EmailViewState extends State { padding: const EdgeInsets.all(8.0), child: Column( children: [ + EmailToolbar( + onButtonPressed: () => {}, + onJumpToSpan: _scrollToNumber + ), Row( children: [ Expanded( diff --git a/lib/structs.dart b/lib/structs.dart index b9dbad3..e99c0d2 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -1,7 +1,6 @@ //data structures import 'dart:typed_data'; -import 'package:markdown/markdown.dart' as md; class GetThreadResponse { final int id; @@ -177,7 +176,7 @@ class AugmentTree { parentIsLettered = false; } else { parentIsLettered = prefix.runes.last >= 'a'.runes.first && - prefix.runes.last <= 'z'.runes.first; + prefix.runes.last <= 'z'.runes.first; } if (prefix.isEmpty) { @@ -197,3 +196,15 @@ class AugmentTree { } } } + + +class MarkdownParsed{ + final String text; + MarkdownParsed({required this.text}); + factory MarkdownParsed.fromJson(Map json){ + return MarkdownParsed( + text: json['md'] ?? '', + ); + } +} + diff --git a/pubspec.yaml b/pubspec.yaml index d3e7a93..c425b10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,9 @@ dependencies: 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: From 3410007f55f1b2ea4c75f2d9bb28c3c5bca2b234 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 18 Jun 2025 21:09:02 -0400 Subject: [PATCH 26/64] clean, and update to match the web --- lib/augment.dart | 1 - lib/collapsableEmailsAndroid.dart | 294 +++++++++++++++++++++--------- lib/emailViewAndroid.dart | 9 +- 3 files changed, 217 insertions(+), 87 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 2b8c5de..8b8fa60 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,6 +1,5 @@ import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/attachmentDownload.dart'; -import 'package:crab_ui/collapsableEmails.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart index 64f2cc8..d89afb4 100644 --- a/lib/collapsableEmailsAndroid.dart +++ b/lib/collapsableEmailsAndroid.dart @@ -7,13 +7,19 @@ import 'package:markdown/markdown.dart' as md; class CollapsableEmails extends StatefulWidget { final List thread; // email id's in the form xyz@gmail.com - final List threadHTML; + // final List threadHTML; + final List threadMarkdown; final String threadIDs; + final String? targetJumpNumbering; + final String? targetViewspecs; CollapsableEmails( {required this.thread, - required this.threadHTML, - required this.threadIDs}); + required this.threadMarkdown, + required this.threadIDs, + this.targetJumpNumbering, + this.targetViewspecs, + }); @override State createState() => _CollapsableEmailsState(); @@ -40,21 +46,41 @@ class _CollapsableEmailsState extends State { }; List tagsCollected = []; - String markdown = ''; + List allMarkdown = []; List> sentinel = []; int level = 0; AugmentTree zoomTreeRoot = AugmentTree(); - late AugmentTree currentZoomNode; + // late AugmentTree currentZoomNode; + late List currentZoomTree = []; bool zoomOut = false; bool zoomIn = true; + late List threadNodes = []; + static bool leftNumbering = false; + static bool rightNumbering = true; + bool showWhole = false; + @override void initState() { super.initState(); - _markdownConverter(); + threadNodes = []; + currentZoomTree = []; + // _markdownConverter(); _serializableData(widget.threadIDs); // this - _markdown2Tree(markdown); - _buildForZooms(); + _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 @@ -62,10 +88,6 @@ class _CollapsableEmailsState extends State { super.dispose(); } - void _markdownConverter() async { - markdown = html2md.convert(widget.threadHTML[0]); - } - void _add2Tree(AugmentTree tree, md.Element node2add) { // adds node to its corresponding place AugmentTree newNode = AugmentTree(); @@ -119,83 +141,67 @@ class _CollapsableEmailsState extends State { } } - void _markdown2Tree(String text) { + void _markdown2Tree(List text) { print("started markdown2tree"); - final List nakedList = md.Document().parseLines(text.split('\n')); - - for (var node in nakedList) { - //maybe do an add function, but isn't this it? - if (node is md.Element) { - // print(node.textContent); - AugmentTree temp = AugmentTree(); - temp.data = node.textContent; - temp.ogTag = node.tag; - if (hirarchyDict.containsKey(node.tag)) { - _add2Tree(zoomTreeRoot, node); + for (int emailsMD = 0; emailsMD < text.length; emailsMD++) { + final List 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 + } } } - - currentZoomNode = zoomTreeRoot; - if (!mounted) return; - setState(() { - _isLoaded = true; - }); + zoomTreeRoot.addNumbering(); + threadNodes.add(zoomTreeRoot); + currentZoomTree.add(zoomTreeRoot); } + + if (!mounted) return; + setState(() { + _isLoaded = true; + }); } - void _goToChildren(int index) async { - final target = currentZoomNode.children[index]; + void _goToChildren(int indexThread, int index) async { + final target = currentZoomTree[indexThread].children[index]; if (target.children.isNotEmpty) { setState(() { - currentZoomNode = target; + currentZoomTree[indexThread] = target; }); } else { print("This child has no further children."); } - - // if (currentZoomNode.children.isNotEmpty) { - // setState(() { - // zoomIn = true; - // zoomOut = true; - // currentZoomNode = currentZoomNode.children[index]; - // if (currentZoomNode.children[index].children.isEmpty) { - // print('disable in'); - // setState(() { - // zoomIn = false; - // }); - // } - // }); - // } else { - // print("disable zoom down"); - // setState(() { - // zoomIn = false; - // }); - // } } - void _goToParent() async { - if (currentZoomNode.parent != null) { + void _goToParent(int indexThread) async { + if (currentZoomTree[indexThread].parent != null) { setState(() { - currentZoomNode = currentZoomNode.parent!; + currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!; }); } else { print("Already at root."); } - - // print("parent ${currentZoomNode.parent}"); - // print("parent ${currentZoomNode.parent!.parent}"); - // if (currentZoomNode.parent != null) { - // setState(() { - // currentZoomNode = currentZoomNode.parent!; - // if (currentZoomNode.parent == null) { - // setState(() { - // zoomOut = false; - // }); - // } - // }); - // } else if (currentZoomNode.parent == null || - // currentZoomNode.parent!.parent == null) { - // print("disable zoom up"); } void _serializableData(String threadID) async { @@ -207,19 +213,23 @@ class _CollapsableEmailsState extends State { }); } - Widget _buildForZooms({Key? key}) { + Widget _buildForZooms(int indexThread) { + // IF I GIVE IT THE INDEX???? if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } - final canZoomOut = currentZoomNode.parent != null; + + final AugmentTree currentZoomNodeForThisEmail = + currentZoomTree[indexThread]; + + final canZoomOut = currentZoomNodeForThisEmail.parent != null; return ListView.builder( - key: key, - itemCount: currentZoomNode.children.length, + itemCount: currentZoomNodeForThisEmail.children.length, itemBuilder: (context, index) { - final childNode = currentZoomNode.children[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( @@ -236,25 +246,46 @@ class _CollapsableEmailsState extends State { spacing: 4.0, children: [ OutlinedButton( - onPressed: canZoomOut ? () => _goToParent() : null, + onPressed: + canZoomOut ? () => _goToParent(indexThread) : null, child: Icon(Icons.north_west_sharp), ), OutlinedButton( - onPressed: - canZoomIn ? () => _goToChildren(index) : null, + 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: currentZoomNode - .children[index].data, // one string of markdown + 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)), + ), + ), ], ), ), @@ -263,6 +294,100 @@ class _CollapsableEmailsState extends State { }, ); } + 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) { @@ -294,9 +419,10 @@ class _CollapsableEmailsState extends State { constraints: BoxConstraints( minHeight: 100, maxHeight: - MediaQuery.of(context).size.height * 0.6), - child: - _buildForZooms(key: ValueKey(currentZoomNode))), + MediaQuery.of(context).size.height * 0.6, + ), + child: _buildForZooms(index), + ), Divider(), ], ); diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart index 33eedd3..7b257a2 100644 --- a/lib/emailViewAndroid.dart +++ b/lib/emailViewAndroid.dart @@ -43,6 +43,9 @@ class _EmailViewState extends State { void _scrollToNumber(String spanId) { // AugmentClasses.handleJump(spanId); } + void _viewSpecs(String command){ + + } @override Widget build(BuildContext context) { @@ -56,7 +59,9 @@ class _EmailViewState extends State { children: [ EmailToolbar( onButtonPressed: () => {}, - onJumpToSpan: _scrollToNumber + onJumpToNumbering: _scrollToNumber, + onViewspecs: _viewSpecs, + ), Row( children: [ @@ -98,7 +103,7 @@ class _EmailViewState extends State { Expanded( child: CollapsableEmails( thread: widget.messages, - threadHTML: widget.emailContent, + threadMarkdown: widget.emailContent, threadIDs: widget.id, ), ), From fb31051b0391f15f681c4fbc6b4f0547f979b199 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 23 Jun 2025 11:40:32 -0400 Subject: [PATCH 27/64] homepage layout fix? --- lib/home_page.dart | 505 ++++++++++++++++++++------------------------- 1 file changed, 226 insertions(+), 279 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index 903a8a4..455c994 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -148,16 +148,6 @@ class _HomeScreenState extends State 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 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: - // // ), - // ], + ); - // ); } }, ); @@ -200,271 +170,248 @@ class _HomeScreenState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - return Scaffold( - key: _scaffoldKey, - drawer: FolderDrawer( - apiService: apiService, - onFolderTap: (folder) { - _emailPageKey.currentState?.updateSelectedFolder(folder); - }, - ), - body: Stack( - children: [ - Row( + return Padding( + padding: const EdgeInsets.fromLTRB(0, 20, 0 , 20), + child: Scaffold( + key: _scaffoldKey, + drawer: FolderDrawer( + apiService: apiService, + onFolderTap: (folder) { + _emailPageKey.currentState?.updateSelectedFolder(folder); + }, + ), + body: Padding( + padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), + child: Stack( children: [ - // Sidebar - if (_isSidebarOpen) - Container( - width: 70, - color: Color.fromARGB(17, 96, 122, 135), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - leading: Icon(Icons.home), - onTap: () { - // Navigate to Home - }, - ), - ListTile( - leading: Icon(Icons.settings), - onTap: () { - // Navigate to Settings - }, - ), - ListTile( - leading: Icon(Icons.email), - onTap: () { - _scaffoldKey.currentState?.openDrawer(); - }, - ), - Spacer(), - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: Alignment.bottomLeft, - child: IconButton( - icon: Icon(Icons.close, color: Colors.white), - onPressed: () { - setState(() { - _isSidebarOpen = false; - }); + Row( + children: [ + // Sidebar + if (_isSidebarOpen) + Container( + width: 70, + color: Color.fromARGB(17, 96, 122, 135), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + leading: Icon(Icons.home), + onTap: () { + // Navigate to Home }, ), - ), - ), - ], - ), - ), - // Main content - Expanded( - child: Column( - children: [ - Container( - padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0), - color: Color.fromARGB(42, 36, 102, 132), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 800, + ListTile( + leading: Icon(Icons.settings), + onTap: () { + // Navigate to Settings + }, + ), + ListTile( + leading: Icon(Icons.email), + onTap: () { + _scaffoldKey.currentState?.openDrawer(); + }, + ), + Spacer(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: Alignment.bottomLeft, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white), + onPressed: () { + setState(() { + _isSidebarOpen = false; + }); + }, ), - child: SizedBox( - height: 40, - child: TextField( - decoration: InputDecoration( - hintText: 'Search...', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.search), + ), + ), + ], + ), + ), + // Main content + Expanded( + child: Column( + children: [ + Container( + padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0), + color: Color.fromARGB(42, 36, 102, 132), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 800, + ), + child: SizedBox( + height: 40, + child: TextField( + decoration: InputDecoration( + hintText: 'Search...', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.search), + ), + onSubmitted: (value) { + if (value.isNotEmpty) { + _performSearch(value, _selectedOption); + } + //this is the input box i mentioned + // if (value == '') { + // setState(() { + // querySearches = false; + // }); + // } + // Future> results = apiService + // .sonicSearch('INBOX', 20, 0, value); + // // print(value); + // print(results); + // setState(() { + // querySearches = true; + // }); + }, + ), ), - onSubmitted: (value) { - if (value.isNotEmpty) { - _performSearch(value, _selectedOption); - } - //this is the input box i mentioned - // if (value == '') { - // setState(() { - // querySearches = false; - // }); - // } - // Future> results = apiService - // .sonicSearch('INBOX', 20, 0, value); - // // print(value); - // print(results); - // setState(() { - // querySearches = true; - // }); - }, ), ), - ), + SizedBox( + width: 8, + ), + Container( + height: 40, + child: ElevatedButton( + onPressed: _showOptionsSearchDialog, + child: Icon(Icons.manage_search), + ), + ) + ], ), - SizedBox( - width: 8, + ), + Container( + padding: EdgeInsets.all(0.0), + color: Color.fromARGB(42, 36, 102, 132), + child: Row( + children: [ + Container( + height: 2, + ) + ], ), - Container( - height: 40, - child: ElevatedButton( - onPressed: _showOptionsSearchDialog, - child: Icon(Icons.manage_search), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.all(0.0), - color: Color.fromARGB(42, 36, 102, 132), - child: Row( - children: [ - Container( - height: 2, - ) - ], - ), - ), - Container( - color: Color.fromARGB(255, 131, 110, 143), - child: TabBar( - controller: _tabController, - isScrollable: true, - tabs: _tabs - .asMap() - .entries - .map((entry) => Tab( - child: Row( - children: [ - Text(entry.value), - if (entry.value != 'Emails') - GestureDetector( - onTap: () => _removeTab(entry.key), - child: Icon(Icons.close, size: 16), - ), - ], - ), - )) - .toList(), - labelColor: Colors.white, - indicatorColor: Colors.white, - ), - ), - Container( - // alignment: Alignment.topLeft, - padding: EdgeInsets.all(8.0), - color: Colors.white, - child: Row( - children: [ - ElevatedButton( - onPressed: () { - _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState - ?.updatePagenation('back'); - }, - child: Icon(Icons.navigate_before), + ), + Container( + color: Color.fromARGB(255, 131, 110, 143), + child: TabBar( + controller: _tabController, + isScrollable: true, + tabs: _tabs + .asMap() + .entries + .map((entry) => Tab( + child: Row( + children: [ + Text(entry.value), + if (entry.value != 'Emails') + GestureDetector( + onTap: () => _removeTab(entry.key), + child: Icon(Icons.close, size: 16), + ), + ], + ), + )) + .toList(), + labelColor: Colors.white, + indicatorColor: Colors.white, ), - Builder( - builder: (context) { - final emailState = _emailPageKey.currentState; - if (emailState == null) { - // Schedule a rebuild once the state is available - Future.microtask(() => setState(() {})); - return Text('Loading...'); - } - - return ValueListenableBuilder( - valueListenable: emailState.currentPageNotifier, - builder: (context, value, _) => Text('$value'), - ); - }, + ), + Container( + // alignment: Alignment.topLeft, + padding: EdgeInsets.all(8.0), + color: Colors.white, + child: Row( + children: [ + ElevatedButton( + onPressed: () { + _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState + ?.updatePagenation('back'); + }, + child: Icon(Icons.navigate_before), + ), + Builder( + builder: (context) { + final emailState = _emailPageKey.currentState; + if (emailState == null) { + // Schedule a rebuild once the state is available + Future.microtask(() => setState(() {})); + return Text('Loading...'); + } + + return ValueListenableBuilder( + valueListenable: emailState.currentPageNotifier, + builder: (context, value, _) => Text('$value'), + ); + }, + ), + ElevatedButton( + onPressed: () { + _emailPageKey.currentState + ?.updatePagenation('next'); + }, + child: Icon(Icons.navigate_next), + ), + ], ), - ElevatedButton( - onPressed: () { - _emailPageKey.currentState - ?.updatePagenation('next'); - }, - child: Icon(Icons.navigate_next), + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _tabs.map((tab) { + return _tabWidgets[tab] ?? + Center(child: Text("No content found")); + // return Center( + // child: EmailPage( + // key: _emailPageKey, + // )); + }).toList(), ), - ], - ), + ), + + // if (_tabs.isEmpty) + // Expanded( + // child: EmailPage(key: _emailPageKey), + // ), + // if (_tabs.isNotEmpty) + // Expanded( + // // child: Text('supposed to be mails'), + // child: TabBarView( + // controller: _tabController, + // children: _tabs + // .map((tab) => Center(child: Text('Results for $tab'))) + // .toList(), + // ), + // ), + ], ), - Expanded( - child: TabBarView( - controller: _tabController, - children: _tabs.map((tab) { - return _tabWidgets[tab] ?? - Center(child: Text("No content found")); - // return Center( - // child: EmailPage( - // key: _emailPageKey, - // )); - }).toList(), - ), - ), - - // if (_tabs.isEmpty) - // Expanded( - // child: EmailPage(key: _emailPageKey), - // ), - // if (_tabs.isNotEmpty) - // Expanded( - // // child: Text('supposed to be mails'), - // child: TabBarView( - // controller: _tabController, - // children: _tabs - // .map((tab) => Center(child: Text('Results for $tab'))) - // .toList(), - // ), - // ), - ], - ), + ), + ], ), + if (!_isSidebarOpen) + Positioned( + bottom: 16, + left: 16, + child: FloatingActionButton( + child: Icon(Icons.menu), + onPressed: () { + setState(() { + _isSidebarOpen = true; + }); + }, + ), + ), ], ), - if (!_isSidebarOpen) - Positioned( - bottom: 16, - left: 16, - child: FloatingActionButton( - child: Icon(Icons.menu), - onPressed: () { - setState(() { - _isSidebarOpen = true; - }); - }, - ), - ), - ], + ), ), ); } } -// void _showPopupMenu(BuildContext context, Offset position) async { -// final RenderBox overlay = -// Overlay.of(context).context.findRenderObject() as RenderBox; - -// await showMenu( -// context: context, -// position: RelativeRect.fromLTRB( -// position.dx, -// position.dy, -// overlay.size.width - position.dx, -// overlay.size.height - position.dy, -// ), -// items: >[ -// PopupMenuItem( -// value: 'Open', -// child: Text('Open'), -// ), -// PopupMenuItem( -// value: 'Reply', -// child: Text('Reply'), -// ), -// PopupMenuItem( -// value: 'Delete', -// child: Text('Delete'), -// ), -// ], -// ); -// } -// } From 5d4bc01a59337a37c3d70a008cf0b59c16be8c99 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 24 Jun 2025 12:06:27 -0400 Subject: [PATCH 28/64] color theme and padding for the non-visible parts of the devices (andorid, ios) --- lib/home_page.dart | 452 +++++++++++++++++++++++---------------------- lib/main.dart | 5 +- 2 files changed, 233 insertions(+), 224 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index 455c994..2849444 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -170,245 +170,251 @@ class _HomeScreenState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(0, 20, 0 , 20), - child: Scaffold( - key: _scaffoldKey, - drawer: FolderDrawer( - apiService: apiService, - onFolderTap: (folder) { - _emailPageKey.currentState?.updateSelectedFolder(folder); - }, - ), - body: Padding( - padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), - child: Stack( - children: [ - Row( + 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, + onFolderTap: (folder) { + _emailPageKey.currentState?.updateSelectedFolder(folder); + }, + ), + body: Scaffold( + backgroundColor: Theme.of(context).colorScheme.onPrimary, + body: Padding( + padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), + child: Stack( children: [ - // Sidebar - if (_isSidebarOpen) - Container( - width: 70, - color: Color.fromARGB(17, 96, 122, 135), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - leading: Icon(Icons.home), - onTap: () { - // Navigate to Home - }, - ), - ListTile( - leading: Icon(Icons.settings), - onTap: () { - // Navigate to Settings - }, - ), - ListTile( - leading: Icon(Icons.email), - onTap: () { - _scaffoldKey.currentState?.openDrawer(); - }, - ), - Spacer(), - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: Alignment.bottomLeft, - child: IconButton( - icon: Icon(Icons.close, color: Colors.white), - onPressed: () { - setState(() { - _isSidebarOpen = false; - }); + Row( + children: [ + // Sidebar + if (_isSidebarOpen) + Container( + width: 70, + color: Color.fromARGB(17, 96, 122, 135), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + leading: Icon(Icons.home), + onTap: () { + // Navigate to Home }, ), - ), - ), - ], - ), - ), - // Main content - Expanded( - child: Column( - children: [ - Container( - padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0), - color: Color.fromARGB(42, 36, 102, 132), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 800, + ListTile( + leading: Icon(Icons.settings), + onTap: () { + // Navigate to Settings + }, + ), + ListTile( + leading: Icon(Icons.email), + onTap: () { + _scaffoldKey.currentState?.openDrawer(); + }, + ), + Spacer(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: Alignment.bottomLeft, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white), + onPressed: () { + setState(() { + _isSidebarOpen = false; + }); + }, ), - child: SizedBox( - height: 40, - child: TextField( - decoration: InputDecoration( - hintText: 'Search...', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.search), + ), + ), + ], + ), + ), + // Main content + Expanded( + child: Column( + children: [ + Container( + padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0), + color: Color.fromARGB(42, 36, 102, 132), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 800, + ), + child: SizedBox( + height: 40, + child: TextField( + decoration: InputDecoration( + hintText: 'Search...', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.search), + ), + onSubmitted: (value) { + if (value.isNotEmpty) { + _performSearch(value, _selectedOption); + } + //this is the input box i mentioned + // if (value == '') { + // setState(() { + // querySearches = false; + // }); + // } + // Future> results = apiService + // .sonicSearch('INBOX', 20, 0, value); + // // print(value); + // print(results); + // setState(() { + // querySearches = true; + // }); + }, + ), ), - onSubmitted: (value) { - if (value.isNotEmpty) { - _performSearch(value, _selectedOption); - } - //this is the input box i mentioned - // if (value == '') { - // setState(() { - // querySearches = false; - // }); - // } - // Future> results = apiService - // .sonicSearch('INBOX', 20, 0, value); - // // print(value); - // print(results); - // setState(() { - // querySearches = true; - // }); - }, ), ), - ), + SizedBox( + width: 8, + ), + Container( + height: 40, + child: ElevatedButton( + onPressed: _showOptionsSearchDialog, + child: Icon(Icons.manage_search), + ), + ) + ], ), - SizedBox( - width: 8, + ), + Container( + padding: EdgeInsets.all(0.0), + color: Color.fromARGB(42, 36, 102, 132), + child: Row( + children: [ + Container( + height: 2, + ) + ], ), - Container( - height: 40, - child: ElevatedButton( - onPressed: _showOptionsSearchDialog, - child: Icon(Icons.manage_search), - ), - ) - ], - ), - ), - Container( - padding: EdgeInsets.all(0.0), - color: Color.fromARGB(42, 36, 102, 132), - child: Row( - children: [ - Container( - height: 2, - ) - ], - ), - ), - Container( - color: Color.fromARGB(255, 131, 110, 143), - child: TabBar( - controller: _tabController, - isScrollable: true, - tabs: _tabs - .asMap() - .entries - .map((entry) => Tab( - child: Row( - children: [ - Text(entry.value), - if (entry.value != 'Emails') - GestureDetector( - onTap: () => _removeTab(entry.key), - child: Icon(Icons.close, size: 16), - ), - ], - ), - )) - .toList(), - labelColor: Colors.white, - indicatorColor: Colors.white, - ), - ), - Container( - // alignment: Alignment.topLeft, - padding: EdgeInsets.all(8.0), - color: Colors.white, - child: Row( - children: [ - ElevatedButton( - onPressed: () { - _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState - ?.updatePagenation('back'); - }, - child: Icon(Icons.navigate_before), + ), + Container( + color: Color.fromARGB(255, 131, 110, 143), + child: TabBar( + controller: _tabController, + isScrollable: true, + tabs: _tabs + .asMap() + .entries + .map((entry) => Tab( + child: Row( + children: [ + Text(entry.value), + if (entry.value != 'Emails') + GestureDetector( + onTap: () => _removeTab(entry.key), + child: Icon(Icons.close, size: 16), + ), + ], + ), + )) + .toList(), + labelColor: Colors.white, + indicatorColor: Colors.white, ), - Builder( - builder: (context) { - final emailState = _emailPageKey.currentState; - if (emailState == null) { - // Schedule a rebuild once the state is available - Future.microtask(() => setState(() {})); - return Text('Loading...'); - } - - return ValueListenableBuilder( - valueListenable: emailState.currentPageNotifier, - builder: (context, value, _) => Text('$value'), - ); - }, + ), + Container( + // alignment: Alignment.topLeft, + padding: EdgeInsets.all(8.0), + color: Colors.white, + child: Row( + children: [ + ElevatedButton( + onPressed: () { + _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState + ?.updatePagenation('back'); + }, + child: Icon(Icons.navigate_before), + ), + Builder( + builder: (context) { + final emailState = _emailPageKey.currentState; + if (emailState == null) { + // Schedule a rebuild once the state is available + Future.microtask(() => setState(() {})); + return Text('Loading...'); + } + + return ValueListenableBuilder( + valueListenable: emailState.currentPageNotifier, + builder: (context, value, _) => Text('$value'), + ); + }, + ), + ElevatedButton( + onPressed: () { + _emailPageKey.currentState + ?.updatePagenation('next'); + }, + child: Icon(Icons.navigate_next), + ), + ], ), - ElevatedButton( - onPressed: () { - _emailPageKey.currentState - ?.updatePagenation('next'); - }, - child: Icon(Icons.navigate_next), + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _tabs.map((tab) { + return _tabWidgets[tab] ?? + Center(child: Text("No content found")); + // return Center( + // child: EmailPage( + // key: _emailPageKey, + // )); + }).toList(), ), - ], - ), + ), + + // if (_tabs.isEmpty) + // Expanded( + // child: EmailPage(key: _emailPageKey), + // ), + // if (_tabs.isNotEmpty) + // Expanded( + // // child: Text('supposed to be mails'), + // child: TabBarView( + // controller: _tabController, + // children: _tabs + // .map((tab) => Center(child: Text('Results for $tab'))) + // .toList(), + // ), + // ), + ], ), - Expanded( - child: TabBarView( - controller: _tabController, - children: _tabs.map((tab) { - return _tabWidgets[tab] ?? - Center(child: Text("No content found")); - // return Center( - // child: EmailPage( - // key: _emailPageKey, - // )); - }).toList(), - ), - ), - - // if (_tabs.isEmpty) - // Expanded( - // child: EmailPage(key: _emailPageKey), - // ), - // if (_tabs.isNotEmpty) - // Expanded( - // // child: Text('supposed to be mails'), - // child: TabBarView( - // controller: _tabController, - // children: _tabs - // .map((tab) => Center(child: Text('Results for $tab'))) - // .toList(), - // ), - // ), - ], - ), + ), + ], ), + if (!_isSidebarOpen) + Positioned( + bottom: 16, + left: 16, + child: FloatingActionButton( + child: Icon(Icons.menu), + onPressed: () { + setState(() { + _isSidebarOpen = true; + }); + }, + ), + ), ], ), - if (!_isSidebarOpen) - Positioned( - bottom: 16, - left: 16, - child: FloatingActionButton( - child: Icon(Icons.menu), - onPressed: () { - setState(() { - _isSidebarOpen = true; - }); - }, - ), - ), - ], + ), ), ), ), diff --git a/lib/main.dart b/lib/main.dart index da90a50..7790390 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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: "/", From add5192b3cde14fb8a9281e6e67413566bb08a33 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 26 Jun 2025 03:57:34 -0400 Subject: [PATCH 29/64] works for the first item filtered --- lib/SonicEmailViewWeb.dart | 12 ++- lib/augment.dart | 145 ++++++++++++++++++------------ lib/collapsableEmailsAndroid.dart | 28 +++--- lib/collapsableEmailsStub.dart | 2 +- lib/collapsableEmailsWeb.dart | 130 +++++++++++++++++++++++---- lib/emailViewAndroid.dart | 6 +- lib/emailViewWeb.dart | 9 ++ 7 files changed, 243 insertions(+), 89 deletions(-) diff --git a/lib/SonicEmailViewWeb.dart b/lib/SonicEmailViewWeb.dart index b5b127a..c1d6881 100644 --- a/lib/SonicEmailViewWeb.dart +++ b/lib/SonicEmailViewWeb.dart @@ -23,10 +23,15 @@ class _SonicEmailViewState extends State { void _scrollToNumber(String spanId) { AugmentClasses.handleJump(spanId); } + void _handleViewspecs(String queryViewspecs) { return; } + void _handleFiltering(String query) { + return; + } + @override void initState() { super.initState(); @@ -92,9 +97,10 @@ class _SonicEmailViewState extends State { Column( children: [ EmailToolbar( - onButtonPressed: () => {}, - onJumpToNumbering: _scrollToNumber, - onViewspecs: _handleViewspecs + onButtonPressed: () => {}, + onJumpToNumbering: _scrollToNumber, + onViewspecs: _handleViewspecs, + onFiltering: _handleFiltering, ), Row( // title of email diff --git a/lib/augment.dart b/lib/augment.dart index 8b8fa60..4be4e23 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -9,10 +9,15 @@ class EmailToolbar extends StatefulWidget { final Function(String) onJumpToNumbering; final Function(String) onViewspecs; final VoidCallback onButtonPressed; + final Function(String) onFiltering; - EmailToolbar( - {Key? key, required this.onButtonPressed, required this.onJumpToNumbering, required this.onViewspecs}) - : super(key: key); + EmailToolbar({ + Key? key, + required this.onButtonPressed, + required this.onJumpToNumbering, + required this.onViewspecs, + required this.onFiltering, + }) : super(key: key); @override _DynamicClassesAugment createState() => _DynamicClassesAugment(); @@ -196,7 +201,7 @@ class _DynamicClassesAugment extends State { ), ), ElevatedButton( - onPressed: () => AugmentClasses.FilterButton(context), + onPressed: () => AugmentClasses().filterButton(context, widget.onFiltering), child: Text('Filter'), ), SizedBox(width: 8), @@ -702,7 +707,6 @@ class AugmentClasses { ElevatedButton(onPressed: () {}, child: Text('OK')), ElevatedButton( onPressed: () { - // AugmentClasses.disableIframePointerEvents(); Navigator.of(context).pop(); }, child: Text('Cancel')), @@ -720,61 +724,92 @@ class AugmentClasses { }); } - void handleFilter() {} - static Future FilterButton(context) async { + Future> searchFilter(String query) async { + return []; + } + + Future filterButton(context, Function(String) onFilteringCallback) async { //this is literally ctrl+F :skull: //idea is to search in file, extract the

tags that contain these //words and highlight, then when zoom, you just jump to that paragraph + bool? numbering = false; + String filterQueue = ''; - // AugmentClasses.disableIframePointerEvents(); await showDialog( - context: context, - builder: (context) => Container( - height: 150, - width: 300, - child: AlertDialog( - title: Text('Filter'), - content: Container( - width: 400, // Set the width to simulate the Windows style - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Set filter:'), - SizedBox( - width: 175, - child: TextField( - maxLines: 1, - decoration: InputDecoration( - border: OutlineInputBorder(), - ), + context: context, + builder: (BuildContext dialogContext) { + // => Container( + // height: 150, + // width: 300, + // child: + return StatefulBuilder(builder: + (BuildContext statefulBuilderContext, StateSetter setState) { + return AlertDialog( + title: const Text('Filter'), + content: SizedBox( + width: 400, // Set the width to simulate the Windows style + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Set filter:'), + SizedBox( + width: 175, + child: TextField( + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), ), - ) - ], - ))))); + onChanged: (value) { + print(value); + filterQueue = value; + }, + ), + ), + SizedBox( + height: 10, + ), + Column(children: [ + Row(children: [ + Checkbox( + value: numbering, + activeColor: + Theme.of(context).colorScheme.tertiary, + onChanged: (newBool) { + setState(() { + numbering = newBool; + }); + }), + Text("Start at top of file") + ]), + ]), + ])), + actions: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text("Cancel")), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop({ + 'filterQueue': filterQueue, + 'numbering': numbering, + }); + }, + child: Text("Apply")), + ], + ); + }); + }, + ).then((result) { + if (result != null) { + print("filter done $result"); + final String query = result['filterQueue']; + onFilteringCallback(query); + } else { + print('cancelled'); + } + }); } - - // static void disableIframePointerEvents() { - // //pretty sure these dont work - // // final iframes = html.document.getElementsByTagName('iframe'); - // final iframes = web.document.getElementsByTagName('iframe'); - // for (var iframe in iframes) { - // // if (iframe is html.Element) { - // // iframe.style.pointerEvents = 'none'; // Disable pointer events - // if (iframe is web.Element) { - // iframe. - // } - // } - // } - - // static void enableIframePointerEvents() { - // // final iframes = html.document.getElementsByTagName('iframe'); - // final iframes = html.document.getElementsByTagName('iframe'); - - // for (var iframe in iframes) { - // if (iframe is html.Element) { - // iframe.style.pointerEvents = 'auto'; // Re-enable pointer events - // } - // } - // } } diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart index d89afb4..ae432e8 100644 --- a/lib/collapsableEmailsAndroid.dart +++ b/lib/collapsableEmailsAndroid.dart @@ -12,13 +12,15 @@ class CollapsableEmails extends StatefulWidget { final String threadIDs; final String? targetJumpNumbering; final String? targetViewspecs; + final String? targetFiltering; - CollapsableEmails( - {required this.thread, - required this.threadMarkdown, - required this.threadIDs, - this.targetJumpNumbering, - this.targetViewspecs, + CollapsableEmails({ + required this.thread, + required this.threadMarkdown, + required this.threadIDs, + this.targetJumpNumbering, + this.targetViewspecs, + this.targetFiltering, }); @override @@ -59,7 +61,6 @@ class _CollapsableEmailsState extends State { static bool rightNumbering = true; bool showWhole = false; - @override void initState() { super.initState(); @@ -69,6 +70,7 @@ class _CollapsableEmailsState extends State { _serializableData(widget.threadIDs); // this _markdown2Tree(widget.threadMarkdown); } + @override void didUpdateWidget(covariant CollapsableEmails oldWidget) { // TODO: implement didUpdateWidget @@ -294,6 +296,7 @@ class _CollapsableEmailsState extends State { }, ); } + void _handleJump(String queryNumbering) { print(queryNumbering); if (queryNumbering.isEmpty) { @@ -416,12 +419,11 @@ class _CollapsableEmailsState extends State { ), if (isExpanded) ConstrainedBox( - constraints: BoxConstraints( - minHeight: 100, - maxHeight: - MediaQuery.of(context).size.height * 0.6, - ), - child: _buildForZooms(index), + constraints: BoxConstraints( + minHeight: 100, + maxHeight: MediaQuery.of(context).size.height * 0.6, + ), + child: _buildForZooms(index), ), Divider(), ], diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart index dd9168f..b523f71 100644 --- a/lib/collapsableEmailsStub.dart +++ b/lib/collapsableEmailsStub.dart @@ -9,7 +9,7 @@ class CollapsableEmails extends StatefulWidget { CollapsableEmails( {required this.thread, required this.threadMarkdown, - required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs}); + required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs, String? targetFiltering}); @override State createState() => _CollapsableEmailsState(); diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 1183259..dfb1309 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -13,6 +13,7 @@ class CollapsableEmails extends StatefulWidget { final String threadIDs; final String? targetJumpNumbering; final String? targetViewspecs; + final String? targetFiltering; const CollapsableEmails({ required this.thread, @@ -21,6 +22,7 @@ class CollapsableEmails extends StatefulWidget { required this.threadIDs, this.targetJumpNumbering, this.targetViewspecs, + this.targetFiltering, }); @override @@ -53,13 +55,16 @@ class _CollapsableEmailsState extends State { int level = 0; AugmentTree zoomTreeRoot = AugmentTree(); // late AugmentTree currentZoomNode; - late List currentZoomTree = []; + late List currentZoomTree = + []; // holds a list of list that holds the list of nodes on the currentzoom bool zoomOut = false; bool zoomIn = true; late List threadNodes = []; static bool leftNumbering = true; static bool rightNumbering = true; bool showWhole = false; + List queryResults = []; + bool _isFilteringActive = false; @override void initState() { @@ -83,6 +88,10 @@ class _CollapsableEmailsState extends State { widget.targetViewspecs != oldWidget.targetViewspecs) { _handleViewspecs(widget.targetViewspecs!); } + if (widget.targetFiltering != null && + widget.targetFiltering != oldWidget.targetFiltering) { + _handleFilterQuery(zoomTreeRoot, widget.targetFiltering!); + } } @override @@ -90,17 +99,6 @@ class _CollapsableEmailsState extends State { 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(); @@ -228,15 +226,89 @@ class _CollapsableEmailsState extends State { } Widget _buildForZooms(int indexThread) { - // IF I GIVE IT THE INDEX???? if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } - final AugmentTree currentZoomNodeForThisEmail = + final AugmentTree + currentZoomNodeForThisEmail = //each index is an email in the thread currentZoomTree[indexThread]; + print(currentZoomNodeForThisEmail.data); + print(currentZoomNodeForThisEmail.children); + print(currentZoomNodeForThisEmail.parent); + final canZoomOut = currentZoomNodeForThisEmail.parent != null; + if (currentZoomNodeForThisEmail.children.isEmpty) { + return ListView.builder( + itemCount: 1, + itemBuilder: (context, index) { + final childNode = currentZoomNodeForThisEmail; + // 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, + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + if (rightNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } return ListView.builder( itemCount: currentZoomNodeForThisEmail.children.length, @@ -285,8 +357,6 @@ class _CollapsableEmailsState extends State { Expanded( child: MarkdownBlock( data: childNode.data, - // data: currentZoomNode - // .children[index].data, // one string of markdown config: MarkdownConfig .darkConfig, // or lightConfig depending on theme ), @@ -389,6 +459,34 @@ class _CollapsableEmailsState extends State { // } } + void _findNodesContainingStrDFS( + AugmentTree node, String query, List results) { + if (node.data.contains(query)) { + results.add(node); + } + for (var child in node.children) { + _findNodesContainingStrDFS(child, query, results); + } + } + + List _handleFilterQuery(AugmentTree root, String query) { + List results = []; + final int targetEmailIndex = _expandedEmails.first; + _findNodesContainingStrDFS(root, query, results); + print(results); + for (var res in results) { + print(res.data); + } + if (results.isNotEmpty) { + setState(() { + currentZoomTree[targetEmailIndex] = results.first; // Update the state + _isFilteringActive = true; + }); + print(currentZoomTree); + } + return results; + } + AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) { //recursively finds the node you mentioned // to find the AugmentTree node corresponding to the `numbering`. diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart index 7b257a2..9f81432 100644 --- a/lib/emailViewAndroid.dart +++ b/lib/emailViewAndroid.dart @@ -47,6 +47,10 @@ class _EmailViewState extends State { } + void _filteringQuery(String query){ + + } + @override Widget build(BuildContext context) { return Scaffold( @@ -61,7 +65,7 @@ class _EmailViewState extends State { onButtonPressed: () => {}, onJumpToNumbering: _scrollToNumber, onViewspecs: _viewSpecs, - + onFiltering: _filteringQuery, ), Row( children: [ diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index ceb4ef1..ae7f624 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -44,6 +44,7 @@ class _EmailViewState extends State { ]; String? _targetJumpNumbering; String? _targetViewspecs; + String? _queryFiltering; @override void initState() { @@ -71,6 +72,12 @@ class _EmailViewState extends State { }); } + void _handleFiltering(String query) { + setState(() { + _queryFiltering = query; + }); + } + // TODO: void _invisibility(String ) //to make purple numbers not visible @override @@ -88,6 +95,7 @@ class _EmailViewState extends State { onJumpToNumbering: _handleJumpRequest, onViewspecs: _handleViewspecsRequest, onButtonPressed: () => {print("email tool bar pressed")}, + onFiltering: _handleFiltering, ), Row( // title of email @@ -133,6 +141,7 @@ class _EmailViewState extends State { threadIDs: widget.id, targetJumpNumbering: _targetJumpNumbering, targetViewspecs: _targetViewspecs, + targetFiltering: _queryFiltering, ), ), ], From 072f8274c04e2ba0e635bf130c0968d5dfb88a8f Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 30 Jun 2025 22:38:36 -0400 Subject: [PATCH 30/64] shows the results of the filtering but going up or down a zoom is disabled, temporarily --- lib/collapsableEmailsWeb.dart | 130 +++++++++++++++++----------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index dfb1309..d4d13ca 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -229,6 +229,7 @@ class _CollapsableEmailsState extends State { if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } + // final List nodesToDisplay; final AugmentTree currentZoomNodeForThisEmail = //each index is an email in the thread @@ -237,77 +238,75 @@ class _CollapsableEmailsState extends State { print(currentZoomNodeForThisEmail.children); print(currentZoomNodeForThisEmail.parent); - + // if (_isFilteringActive) { + // nodesToDisplay = queryResults; + // } else { + // nodesToDisplay = currentZoomNodeForThisEmail.children; + // } final canZoomOut = currentZoomNodeForThisEmail.parent != null; - if (currentZoomNodeForThisEmail.children.isEmpty) { + + if (_isFilteringActive) { return ListView.builder( - itemCount: 1, - itemBuilder: (context, index) { - final childNode = currentZoomNodeForThisEmail; - // 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, + itemCount: queryResults.length, + itemBuilder: (context, index) { + AugmentTree childNode = queryResults[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: Material( + elevation: 1, + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - OutlinedButton( - onPressed: - canZoomOut ? () => _goToParent(indexThread) : null, - child: Icon(Icons.north_west_sharp), + Wrap( + spacing: 4.0, + children: [ + OutlinedButton( + onPressed: null, + child: Icon(Icons.north_west_sharp), + ), + OutlinedButton( + onPressed: null, + child: Icon(Icons.south_east_sharp), + ), + ], ), - OutlinedButton( - onPressed: canZoomIn - ? () => _goToChildren(indexThread, index) - : null, - child: Icon(Icons.south_east_sharp), + SizedBox(width: 12.0), + if (leftNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + Expanded( + child: MarkdownBlock( + data: childNode.data, + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), ), + if (rightNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), ], ), - SizedBox(width: 12.0), - if (leftNumbering) - Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), - child: Text( - childNode.numbering, - style: - TextStyle(color: Color(Colors.purple[400]!.value)), - ), - ), - Expanded( - child: MarkdownBlock( - data: childNode.data, - config: MarkdownConfig - .darkConfig, // or lightConfig depending on theme - ), - ), - if (rightNumbering) - Padding( - padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), - child: Text( - childNode.numbering, - style: - TextStyle(color: Color(Colors.purple[400]!.value)), - ), - ), - ], + ), ), - ), - ), - ); - }, - ); + ); + }); } return ListView.builder( @@ -315,7 +314,6 @@ class _CollapsableEmailsState extends State { 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( @@ -479,8 +477,10 @@ class _CollapsableEmailsState extends State { } if (results.isNotEmpty) { setState(() { - currentZoomTree[targetEmailIndex] = results.first; // Update the state + queryResults = results; + // currentZoomTree[targetEmailIndex] = results.first; // Update the state _isFilteringActive = true; + currentZoomTree[targetEmailIndex] = root; }); print(currentZoomTree); } From 80a0c96e0fa60877620f9f2c3f174d8b04cdb1f7 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 2 Jul 2025 15:08:18 -0400 Subject: [PATCH 31/64] filtering finished, with zooming out and in --- lib/collapsableEmailsWeb.dart | 50 +++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index d4d13ca..73b8c2d 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -205,6 +205,22 @@ class _CollapsableEmailsState extends State { } } + void _goToChildrenFiltering( + int indexThread, int index, AugmentTree node) async { + final target = node; + if (target.children.isNotEmpty) { + setState(() { + currentZoomTree[indexThread] = target; + _isFilteringActive = false; + }); + for (var child in target.children) { + print(child.data); + } + } else { + print("This child has no further children."); + } + } + void _goToParent(int indexThread) async { if (currentZoomTree[indexThread].parent != null) { setState(() { @@ -215,6 +231,16 @@ class _CollapsableEmailsState extends State { } } + void _goToParentFiltering(int indexThread, AugmentTree node) async { + if (node.parent != null) { + setState(() { + currentZoomTree[indexThread] = node.parent!; + }); + } else { + print("Already at root."); + } + } + void _serializableData(String threadID) async { emailsInThread = await ApiService().threadsInSerializable(threadID); print("done thread serializable"); @@ -229,9 +255,8 @@ class _CollapsableEmailsState extends State { if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } - // final List nodesToDisplay; - final AugmentTree + AugmentTree currentZoomNodeForThisEmail = //each index is an email in the thread currentZoomTree[indexThread]; print(currentZoomNodeForThisEmail.data); @@ -250,6 +275,7 @@ class _CollapsableEmailsState extends State { itemCount: queryResults.length, itemBuilder: (context, index) { AugmentTree childNode = queryResults[index]; + bool canZoomIn = childNode.children.isNotEmpty; return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Material( @@ -266,11 +292,19 @@ class _CollapsableEmailsState extends State { spacing: 4.0, children: [ OutlinedButton( - onPressed: null, + onPressed: () => { + setState(() { + _goToParentFiltering(indexThread, childNode); + _isFilteringActive = false; + }) + }, child: Icon(Icons.north_west_sharp), ), OutlinedButton( - onPressed: null, + onPressed: canZoomIn + ? () => _goToChildrenFiltering( + indexThread, index, childNode) + : null, child: Icon(Icons.south_east_sharp), ), ], @@ -281,8 +315,8 @@ class _CollapsableEmailsState extends State { padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), child: Text( childNode.numbering, - style: - TextStyle(color: Color(Colors.purple[400]!.value)), + style: TextStyle( + color: Color(Colors.purple[400]!.value)), ), ), Expanded( @@ -297,8 +331,8 @@ class _CollapsableEmailsState extends State { padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), child: Text( childNode.numbering, - style: - TextStyle(color: Color(Colors.purple[400]!.value)), + style: TextStyle( + color: Color(Colors.purple[400]!.value)), ), ), ], From dcb93a18c690a17a7e716bc619699020d86ab337 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 8 Jul 2025 13:24:54 -0400 Subject: [PATCH 32/64] added copying the link to your device --- lib/augment.dart | 161 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 3 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 4be4e23..42f2d3a 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,15 +1,19 @@ import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/attachmentDownload.dart'; +import 'package:crab_ui/collapsableEmails.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'attachmentWidget.dart'; +import 'package:flutter/services.dart'; class EmailToolbar extends StatefulWidget { final Function(String) onJumpToNumbering; final Function(String) onViewspecs; final VoidCallback onButtonPressed; final Function(String) onFiltering; + final List emails; + final String subject; EmailToolbar({ Key? key, @@ -17,6 +21,8 @@ class EmailToolbar extends StatefulWidget { required this.onJumpToNumbering, required this.onViewspecs, required this.onFiltering, + required this.emails, + required this.subject, }) : super(key: key); @override @@ -27,6 +33,8 @@ class _DynamicClassesAugment extends State { String selectedClass = 'Class 1'; TextEditingController _jumpController = TextEditingController(); TextEditingController _viewspecsController = TextEditingController(); + AugmentClasses? localAugment; + List? emailsInThread; // late final FocusNode _JumpItemfocusNode; // late final FocusNode _viewSpecsfocusNode; @@ -47,6 +55,17 @@ class _DynamicClassesAugment extends State { // _viewSpecsfocusNode.addListener(() { // setState(() => _viewSpecsHasFocus = _viewSpecsfocusNode.hasFocus); // }); + _serializableData(widget.emails); + } + + void _serializableData(List threadID) async { + // emailsInThread = await ApiService().threadsInSerializable(); + print("done thread serializable"); + + if (!mounted) return; + // setState(() { + // _isLoaded = true; + // }); } @override @@ -201,7 +220,8 @@ class _DynamicClassesAugment extends State { ), ), ElevatedButton( - onPressed: () => AugmentClasses().filterButton(context, widget.onFiltering), + onPressed: () => + AugmentClasses().filterButton(context, widget.onFiltering), child: Text('Filter'), ), SizedBox(width: 8), @@ -211,7 +231,8 @@ class _DynamicClassesAugment extends State { ), // SizedBox(width: 8), ElevatedButton( - onPressed: AugmentClasses.handleFind, + onPressed: () => AugmentClasses() + .handleCreateLink(context, widget.emails, widget.subject), child: Text('Create Link'), ), ElevatedButton( @@ -226,6 +247,13 @@ class _DynamicClassesAugment extends State { } class AugmentClasses { + CollapsableEmails? localCollapsable; + String? nameOfDocument; + + // AugmentClasses(CollapsableEmails localCollapsable) { + // localCollapsable = localCollapsable; + // } + static OverlayEntry? _overlayEntry; static String? selectedFolder; // Manage selected folder at the class level @@ -491,6 +519,131 @@ class AugmentClasses { print("Find button pressed"); } + _copyLink(String anchor, String target, String format, String viewspecs, + String nameOfDocument) { + String form = "$anchor < $nameOfDocument, $target :$viewspecs >"; + final link = ClipboardData(text: form); + Clipboard.setData(link); + } + + Future handleCreateLink(BuildContext context, + List emailsInThread, String nameOfDocument) async { + print("create link button pressed"); + final TextEditingController targetController = TextEditingController(); + final TextEditingController anchorController = TextEditingController(); + final TextEditingController viewspecsController = TextEditingController(); + final TextEditingController formatController = TextEditingController(); + + // String anchorPhrase = ''; + String format = 'augment'; + // String target = ''; + // String viewspecs = ''; + + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Create URL Link'), + content: SizedBox( + height: 400, + child: Column( + children: [ + Row( + children: [ + Text("Which email? "), + SizedBox( + width: 350.0, + child: Text(nameOfDocument), + // child: ListView.builder( + // itemCount: emailsInThread.length, + // itemBuilder: (context, index) { + // // var item = emailsInThread[index]; + // // ApiService(). + // return ListTile( + // title: Text(nameOfDocument), + // ); + // }), + ) + ], + ), + Row( + children: [ + Text("Link to target item at: "), + SizedBox( + width: 350.0, + child: TextField( + controller: targetController, + autofocus: true, + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + SizedBox(height: 8), + Row( + children: [ + ElevatedButton( + onPressed: () => ViewSpecsButton(context), + child: Text("Viewspecs:")), + SizedBox( + width: 150.0, + child: TextField( + controller: viewspecsController, + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + SizedBox(height: 8), + Row( + children: [ + Text("Using anchor phrase: "), + SizedBox( + width: 150.0, + child: TextField( + controller: anchorController, + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + SizedBox(height: 8), + Row( + children: [ + Text("Using link format: "), + SizedBox( + width: 250.0, + child: Text("Augment"), + ), + ], + ) + ], + ), + ), + actions: [ + ElevatedButton( + onPressed: () => { + _copyLink(anchorController.text, targetController.text, format, viewspecsController.text, + nameOfDocument), + Navigator.of(context).pop() + }, + child: Text("OK")), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: Text("Cancel")), + ElevatedButton(onPressed: null, child: Text("Help")), + ], + )); + } + static void handleStop() { print("Stop button pressed"); } @@ -728,7 +881,8 @@ class AugmentClasses { return []; } - Future filterButton(context, Function(String) onFilteringCallback) async { + Future filterButton( + context, Function(String) onFilteringCallback) async { //this is literally ctrl+F :skull: //idea is to search in file, extract the

tags that contain these //words and highlight, then when zoom, you just jump to that paragraph @@ -756,6 +910,7 @@ class AugmentClasses { SizedBox( width: 175, child: TextField( + autofocus: true, maxLines: 1, decoration: const InputDecoration( border: OutlineInputBorder(), From 4177e29e8bbaa0fac383497342ca30d2c89b8d41 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 8 Jul 2025 13:25:53 -0400 Subject: [PATCH 33/64] cleaning --- lib/email.dart | 2 -- lib/home_page.dart | 3 --- 2 files changed, 5 deletions(-) diff --git a/lib/email.dart b/lib/email.dart index 0b1ded3..f922d61 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -31,8 +31,6 @@ class EmailListScreen extends StatelessWidget { onTap: () async { List emailContent = // list of the html await getEmailContent(email.messages, folder); - // print("this is what email.messages look like in email.dart ${email.messages}"); - // List emailIds = email.messages; print(email.messages); //email ids of the thread Navigator.push( diff --git a/lib/home_page.dart b/lib/home_page.dart index 2849444..cbd2e76 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -2,12 +2,9 @@ import 'package:crab_ui/sonicEmailView.dart'; import 'folder_drawer.dart'; import 'structs.dart'; -// import 'package:flutter/widgets.dart'; import 'api_service.dart'; import 'package:flutter/material.dart'; import 'email.dart'; -// import 'package:shared_preferences/shared_preferences.dart'; -// import 'serialize.dart'; class HomeScreen extends StatefulWidget { @override From 0b117abd880a3f4f6996c71388a582d1b222eaed Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 15 Jul 2025 12:26:16 -0400 Subject: [PATCH 34/64] deep linking added for only the first email in a thread --- lib/augment.dart | 118 +++++++++++++++++-- lib/collapsableEmailsWeb.dart | 21 +++- lib/routingHandler.dart | 205 ++++++++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 lib/routingHandler.dart diff --git a/lib/augment.dart b/lib/augment.dart index 42f2d3a..992f118 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -3,9 +3,11 @@ import 'package:crab_ui/attachmentDownload.dart'; import 'package:crab_ui/collapsableEmails.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'attachmentWidget.dart'; import 'package:flutter/services.dart'; +import 'routingHandler.dart'; class EmailToolbar extends StatefulWidget { final Function(String) onJumpToNumbering; @@ -14,6 +16,7 @@ class EmailToolbar extends StatefulWidget { final Function(String) onFiltering; final List emails; final String subject; + late AugmentTree? rootAugment; EmailToolbar({ Key? key, @@ -23,6 +26,7 @@ class EmailToolbar extends StatefulWidget { required this.onFiltering, required this.emails, required this.subject, + required this.rootAugment, }) : super(key: key); @override @@ -33,7 +37,7 @@ class _DynamicClassesAugment extends State { String selectedClass = 'Class 1'; TextEditingController _jumpController = TextEditingController(); TextEditingController _viewspecsController = TextEditingController(); - AugmentClasses? localAugment; + AugmentTree? localAugment; List? emailsInThread; // late final FocusNode _JumpItemfocusNode; @@ -55,6 +59,7 @@ class _DynamicClassesAugment extends State { // _viewSpecsfocusNode.addListener(() { // setState(() => _viewSpecsHasFocus = _viewSpecsfocusNode.hasFocus); // }); + localAugment = widget.rootAugment; _serializableData(widget.emails); } @@ -236,7 +241,9 @@ class _DynamicClassesAugment extends State { child: Text('Create Link'), ), ElevatedButton( - onPressed: AugmentClasses.handleFind, + // onPressed: () => localAugment!.handlePaste(context), + onPressed: () => + AugmentClasses().handlePaste(context, widget.emails[0]), child: Text('Paste Link'), ), ], @@ -249,6 +256,11 @@ class _DynamicClassesAugment extends State { class AugmentClasses { CollapsableEmails? localCollapsable; String? nameOfDocument; + AugmentTree? rootTree; + + void setRootTree(AugmentTree aTree) { + rootTree = aTree; + } // AugmentClasses(CollapsableEmails localCollapsable) { // localCollapsable = localCollapsable; @@ -519,6 +531,26 @@ class AugmentClasses { print("Find button pressed"); } + AugmentTree? _findAugmentNode(String target, AugmentTree node, int index) { + // so the ideqa is that since the numbering its quite linear, meaning that it tells you where it goes, + // thus i've thought the amount of max moves are only the length of the string of the target + //e.g. if we have a target of 1e9, its steps are the same or time complexity as if it were 1a1, or 99z99 + // since each number or letter tells us which is the index in this array, genius ik + // thus first one needs another function from converting from alphabetical to numbers + if (node.numbering[index] == target[index]) { + _findAugmentNode(target, node.children[index++], index++); + } + } + + bool _checkValidTarget(String target) { + target = target.trim(); + //find if the target exists, + //recursive? + _findAugmentNode(target, rootTree!, 0); + + return false; + } + _copyLink(String anchor, String target, String format, String viewspecs, String nameOfDocument) { String form = "$anchor < $nameOfDocument, $target :$viewspecs >"; @@ -631,7 +663,13 @@ class AugmentClasses { actions: [ ElevatedButton( onPressed: () => { - _copyLink(anchorController.text, targetController.text, format, viewspecsController.text, + // _checkValidTarget(targetController.text), + + _copyLink( + anchorController.text, + targetController.text, + format, + viewspecsController.text, nameOfDocument), Navigator.of(context).pop() }, @@ -644,6 +682,72 @@ class AugmentClasses { )); } + Future handlePaste(BuildContext context, String emailID) async { + final TextEditingController gotoLink = TextEditingController(); + + Routinghandler localRouting; + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("GOTO Link"), + content: SizedBox( + height: 400, + child: Column( + children: [ + Row( + children: [ + Text("Paste link to go: "), + SizedBox( + width: 350, + child: TextField( + controller: gotoLink, + maxLines: 1, + autofocus: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + )) + ], + ) + ], + ), + ), + actions: [ + ElevatedButton( + onPressed: () { + print('pressed'); + // i need this shit to be processed into args + print("email_id given $emailID"); + Navigator.of(context).pop(); + final localRouting = + Routinghandler(gotoLink.text, emailID); + final String subject = + localRouting.docName; // This is your :subject + final String target = + localRouting.target; // This is your :target + final String viewspecs = + localRouting.viewspecs; // This is your :viewspecs + final String finalEmailID = emailID; + + final encodedSubject = Uri.encodeComponent(subject); + final encodedTarget = Uri.encodeComponent(target); + final encodedViewspecs = Uri.encodeComponent(viewspecs); + final encodedEmailID = Uri.encodeComponent(finalEmailID); + print("emailID $encodedEmailID"); + String link = + "/email/$encodedSubject/$encodedTarget/$encodedViewspecs/$encodedEmailID"; + GoRouter.of(context).go(link); + }, + child: Text("OK")), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text("Cancel")) + ], + )); + } + static void handleStop() { print("Stop button pressed"); } @@ -655,12 +759,8 @@ class AugmentClasses { static void invisibility(String htmlClass) {} static Future JumpButton(BuildContext context) async { - // FocusNode textFieldFocusNode = FocusNode(); - - // AugmentClasses.disableIframePointerEvents(); await showDialog( barrierDismissible: true, - // barrierColor: Colors.yellow, context: context, builder: (context) => AlertDialog( title: Text('Jump Item:'), @@ -892,10 +992,6 @@ class AugmentClasses { await showDialog( context: context, builder: (BuildContext dialogContext) { - // => Container( - // height: 150, - // width: 300, - // child: return StatefulBuilder(builder: (BuildContext statefulBuilderContext, StateSetter setState) { return AlertDialog( diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 73b8c2d..3300e5e 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -1,8 +1,6 @@ -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; @@ -14,6 +12,7 @@ class CollapsableEmails extends StatefulWidget { final String? targetJumpNumbering; final String? targetViewspecs; final String? targetFiltering; + final String? nameOfDocument; const CollapsableEmails({ required this.thread, @@ -23,10 +22,15 @@ class CollapsableEmails extends StatefulWidget { this.targetJumpNumbering, this.targetViewspecs, this.targetFiltering, + this.nameOfDocument, }); @override State createState() => _CollapsableEmailsState(); + + AugmentTree? getAugmentRoot() { + return _CollapsableEmailsState().getAugmentRoot(); + } } class _CollapsableEmailsState extends State { @@ -63,7 +67,7 @@ class _CollapsableEmailsState extends State { static bool leftNumbering = true; static bool rightNumbering = true; bool showWhole = false; - List queryResults = []; + List queryResults = []; //results of conducting filtering bool _isFilteringActive = false; @override @@ -99,6 +103,14 @@ class _CollapsableEmailsState extends State { super.dispose(); } + List getThreads() { + return emailsInThread; + } + + AugmentTree getAugmentRoot() { + return zoomTreeRoot; + } + void _add2Tree(AugmentTree tree, md.Element node2add) { // adds node to its corresponding place AugmentTree newNode = AugmentTree(); @@ -252,6 +264,8 @@ class _CollapsableEmailsState extends State { } Widget _buildForZooms(int indexThread) { + // index of email in thread, currentZoomTree, + // if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } @@ -568,7 +582,6 @@ class _CollapsableEmailsState extends State { maxHeight: MediaQuery.of(context).size.height * 0.6, ), child: _buildForZooms(index), //show the tree - // child: _buildForZooms(key: ValueKey(currentZoomNode)), ), Divider(), ], diff --git a/lib/routingHandler.dart b/lib/routingHandler.dart new file mode 100644 index 0000000..f3052a6 --- /dev/null +++ b/lib/routingHandler.dart @@ -0,0 +1,205 @@ +import 'package:crab_ui/collapsableEmails.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:markdown_widget/markdown_widget.dart'; +import 'api_service.dart'; + +class Routinghandler extends StatefulWidget { + Routinghandler(String link, emailID) { + bool anchorDone = false; + + bool docNameDone = false; + + bool viewspecsDone = false; + + bool targetDone = false; + + for (int letter = 0; letter < link.length; letter++) { + if (!anchorDone) { + if (link[letter] != '<') { + //when the anchor hasnt been dissected + anchor += link[letter]; + } else { + anchorDone = true; + } + } else if (!docNameDone) { + if (link[letter] != ',') { + //when the docName hasnt been dissected + docName += link[letter]; + } else { + docNameDone = true; + } + } else if (!targetDone) { + if (link[letter] != ':') { + target += link[letter]; + } else { + targetDone = true; + } + } else if (!viewspecsDone) { + if (link[letter] != '>') { + //when the docName hasnt been dissected + viewspecs += link[letter]; + } else { + viewspecsDone = true; + } + } + } + anchor = anchor.trim(); + docName = docName.trim(); + target = target.trim(); + viewspecs = viewspecs.trim(); + print("inside constructor uwu $emailID"); + emailID = emailID.trim(); + } + Routinghandler.fromParameters(String anchor, String docName, String target, + String viewspecs, String emailID) { + this.anchor = anchor; + this.docName = docName; + this.viewspecs = viewspecs; + this.target = target; + this.emailID = emailID; + } + Routinghandler.copyConstructor(Routinghandler other) { + anchor = other.anchor; + docName = other.docName; + viewspecs = other.viewspecs; + target = other.target; + emailID = other.emailID; + } + + String anchor = ''; + String docName = ''; + String viewspecs = ''; + String target = ''; + String emailID = ''; + + void goToLink() { + // bool anchorDone = false; + + // bool docNameDone = false; + + // bool viewspecsDone = false; + + // bool targetDone = false; + + // for (int letter = 0; letter < link.length; letter++) { + // if (!anchorDone) { + // if (link[letter] != '<') { + // //when the anchor hasnt been dissected + // anchor += link[letter]; + // } else { + // anchorDone = true; + // } + // } else if (!docNameDone) { + // if (link[letter] != ',') { + // //when the docName hasnt been dissected + // docName += link[letter]; + // } else { + // docNameDone = true; + // } + // } else if (!targetDone) { + // if (link[letter] != ':') { + // target += link[letter]; + // } else { + // targetDone = true; + // } + // } else if (!viewspecsDone) { + // if (link[letter] != '>') { + // //when the docName hasnt been dissected + // viewspecs += link[letter]; + // } else { + // viewspecsDone = true; + // } + // } + // } + print("anchor $anchor"); + print("docName $docName"); + print("target $target"); + print("viewspecs $viewspecs"); + print("emailID $emailID"); + //now it should open a widget in that part + //maybe i need a rewrite + } + + String getEmailID() { + return emailID; + } + + @override + State createState() => _RoutingHandlerState(); +} + +class _RoutingHandlerState extends State { + List markdownContent = []; + bool _isLoaded = false; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _loadMarkdown(); + } + + Future _loadMarkdown() async { + String folder = ApiService.currFolder; + // print(folder); + print(widget.getEmailID()); + String emailID = widget.emailID; + print("inside _loadMarkdown in routinghandler $emailID"); + markdownContent = + await ApiService().fetchMarkdownContent([emailID], "INBOX"); + // print(markdownContent); + setState(() { + _isLoaded = true; + }); + } + + @override + Widget build(BuildContext context) { + if (!_isLoaded) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Scaffold( + appBar: AppBar( + title: Text("Routing Handler"), + leading: IconButton( + onPressed: () { + GoRouter.of(context).go('/home'); + }, + icon: const Icon(Icons.arrow_back_ios)), + ), + body: ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100, + maxHeight: MediaQuery.of(context).size.height * 0.7, + ), + child: SingleChildScrollView( + child:MarkdownBlock(data: markdownContent[0]))), + ); + } +} + +class LinkViewer extends StatefulWidget { + const LinkViewer({super.key}); + + @override + State createState() => _LinkViewerState(); +} + +class _LinkViewerState extends State { + @override + Widget build(BuildContext context) { + // this should be a class that opens a popup of the email on the view it wants + + return Scaffold( + appBar: AppBar( + title: Text('url viewer'), + ), + body: Column( + children: [], + )); + } +} From bde05197aee3f480e33057bcb9950125e709eb11 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 15 Jul 2025 12:26:32 -0400 Subject: [PATCH 35/64] added go_router for deeplinking --- lib/main.dart | 60 ++++++++++++++++++++++++++++++++++++++++++--------- pubspec.yaml | 1 + 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 7790390..f5997de 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,18 @@ import 'package:crab_ui/contact.dart'; +import 'package:crab_ui/email.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'home_page.dart'; import 'login.dart'; - +import 'package:go_router/go_router.dart'; +import 'routingHandler.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); - runApp(HyM()); + runApp(ChangeNotifierProvider( + create: (context) => AuthService(), + child: HyM(), + )); } class HyM extends StatelessWidget { @@ -15,22 +21,56 @@ class HyM extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + final GoRouter _router = GoRouter( + // refreshListenable: , + initialLocation: '/', + routes: [ + GoRoute( + path: "/", + builder: (context, state) => SplashScreen(), + ), + GoRoute( + path: "/login", + builder: (context, state) => const LoginPage(), + ), + GoRoute( + path: "/home", + builder: (context, state) => HomeScreen(), + ), + GoRoute( + path: "/contacts", + builder: (context, state) => ContactsPage(), + ), + GoRoute( + path: "/email/:subject/:target/:viewspecs/:emailID", + builder: (context, state) { + final subject = state.pathParameters['subject']!; + final target = state.pathParameters['target']!; + final viewspecs = state.pathParameters['viewspecs']!; + final emailId = state.pathParameters['emailID']!; + return Routinghandler.fromParameters("main anchor", subject, target, viewspecs, emailId); + }), + ]); + return MaterialApp.router( debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.light(), useMaterial3: true, ), title: 'HyM', + routerConfig: _router, // home: HomeScreen(), - initialRoute: "/", - routes: { - "/": (context) => SplashScreen(), - "/login": (context) => const LoginPage(), - "/home": (context) => HomeScreen(), - "/contacts": (context) => ContactsPage(), - }, + // routes: { + // "/": (context) => SplashScreen(), + // "/login": (context) => const LoginPage(), + // "/home": (context) => HomeScreen(), + // "/contacts": (context) => ContactsPage(), + // GoRoute( + // path: + // ) + // "/email": (context) => EmailListScreen(), + // }, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index c425b10..fd454e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: html2md: ^1.3.2 markdown_widget: ^2.3.2+8 markdown: ^7.3.0 + go_router: ^16.0.0 dev_dependencies: flutter_test: From 4d75674e8e68c0321b68694f3b8f09feeb928502 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 18 Jul 2025 10:57:13 -0400 Subject: [PATCH 36/64] instance rather than a new classs --- lib/emailViewWeb.dart | 45 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index ae7f624..a73c040 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -1,7 +1,6 @@ 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'; @@ -35,8 +34,30 @@ class _EmailViewState extends State { late Key iframeKey; late String currentContent; late String viewTypeId; //make this a list too??? - Future>>? _markerPositionsFuture; // TextEditingController _jumpController = TextEditingController(); + late EmailToolbar toolbarInstance = EmailToolbar( + onJumpToNumbering: _handleJumpRequest, + onViewspecs: _handleViewspecsRequest, + onButtonPressed: () => {print("email tool bar pressed")}, + onFiltering: _handleFiltering, + emails: widget.messages, + subject: widget.subject, + rootAugment: localCollapsable.getAugmentRoot(), + ); + + late CollapsableEmails localCollapsable = CollapsableEmails( + //change here + thread: widget.messages, //this wont work in serializable + // threadHTML: widget.emailContent, // old html + threadMarkdown: widget.emailContent, + threadIDs: widget.id, + targetJumpNumbering: _targetJumpNumbering, + targetViewspecs: _targetViewspecs, + targetFiltering: _queryFiltering, + nameOfDocument: widget.subject, + ); + + final hardcodedMarkers = [ {'id': 'marker1', 'x': 50, 'y': 100}, {'id': 'marker2', 'x': 150, 'y': 200}, @@ -78,11 +99,11 @@ class _EmailViewState extends State { }); } - // TODO: void _invisibility(String ) //to make purple numbers not visible @override Widget build(BuildContext context) { ApiService.currThreadID = widget.id; + // AugmentClasses localAugment = AugmentClasses(localCollapsable); return Scaffold( appBar: AppBar( title: Text(widget.name), @@ -91,12 +112,7 @@ class _EmailViewState extends State { children: [ Column( children: [ - EmailToolbar( - onJumpToNumbering: _handleJumpRequest, - onViewspecs: _handleViewspecsRequest, - onButtonPressed: () => {print("email tool bar pressed")}, - onFiltering: _handleFiltering, - ), + toolbarInstance, Row( // title of email children: [ @@ -133,16 +149,7 @@ class _EmailViewState extends State { ], ), 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, - targetFiltering: _queryFiltering, - ), + child: localCollapsable, ), ], ), From 2465201b0b4e5190feb4e47c732f2b161323aae3 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 18 Jul 2025 10:57:50 -0400 Subject: [PATCH 37/64] O(1) optimization --- lib/collapsableEmailsWeb.dart | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart index 3300e5e..b758783 100644 --- a/lib/collapsableEmailsWeb.dart +++ b/lib/collapsableEmailsWeb.dart @@ -176,22 +176,9 @@ class _CollapsableEmailsState extends State { AugmentTree temp = AugmentTree(); temp.data = node.textContent; temp.ogTag = node.tag; - if (node.tag == 'h1') { - // make this O(1) + //why did i do this??? + if ( hirarchyDict.containsKey(node.tag)) { _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 } } } From 433394a74a605255632d5ea37b53aa266ca8e9d6 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 22 Jul 2025 23:10:45 -0400 Subject: [PATCH 38/64] compose widget where you can write the email --- lib/Compose.dart | 165 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 lib/Compose.dart diff --git a/lib/Compose.dart b/lib/Compose.dart new file mode 100644 index 0000000..27081ee --- /dev/null +++ b/lib/Compose.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:super_editor/super_editor.dart'; +import 'package:super_editor_markdown/super_editor_markdown.dart'; + +class ComposeEmail extends StatefulWidget { + final VoidCallback onClose; + final Function(String) onMinimize; + final Function(String) onSendMessage; + const ComposeEmail({ + Key? key, + required this.onMinimize, + required this.onClose, + required this.onSendMessage, + }) : super(key: key); + + @override + _ComposeEmailState createState() => _ComposeEmailState(); +} + +class _ComposeEmailState extends State { + // if one were to alter a mutableDocument, one should only alter the document through EditRequest to the Editor + late final Editor _editor; + late final MutableDocument _document; + late final MutableDocumentComposer _composer; + TextEditingController _emailRecipientController = TextEditingController(); + + @override + void initState() { + super.initState(); + + _document = MutableDocument(nodes: [ + ParagraphNode( + id: Editor.createNodeId(), + text: AttributedText("hello world!"), + ) + ]); + _composer = MutableDocumentComposer(); + _editor = + createDefaultDocumentEditor(document: _document!, composer: _composer!); + } + + @override + void dispose() { + _editor.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Positioned( + bottom: 10.0, + right: 10.0, + child: Material( + elevation: 8.0, + child: Container( + width: 600.0, + height: 616.0, + decoration: BoxDecoration( + color: Colors.white, + ), + child: Column(children: [ + AppBar( + title: const Text("new message"), + actions: [ + IconButton( + onPressed: () { + //TODO: implement minimize, and submit email to drafts + widget.onClose(); + }, + icon: Icon(Icons.minimize, color: Colors.grey[600])), + IconButton( + onPressed: () { + //TODO: implement that maximizing + }, + icon: Icon(Icons.maximize, color: Colors.grey[600])), + IconButton( + onPressed: () { + widget.onClose(); + }, + icon: Icon(Icons.close, color: Colors.grey[600])), + ], + ), + Container( + // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS + width: 500.0, + height: 40.0, + child: Row( + children: [ + TextButton(onPressed: () {}, child: Text("To:")), + Expanded( + child: TextField( + controller: _emailRecipientController, + ), + ), + TextButton(onPressed: () {}, child: Text("Cc")), + SizedBox( + width: 4.0, + ), + TextButton(onPressed: () {}, child: Text("Bcc")), + ], + ), + ), + Expanded( + //here the widget goes + child: SuperEditor( + editor: _editor!, + plugins: {MarkdownInlineUpstreamSyntaxPlugin()}, + stylesheet: Stylesheet( + rules: [StyleRule(BlockSelector.all, (doc, docNode) { + return { + Styles.maxWidth: 640.0, + Styles.padding: const CascadingPadding.symmetric(horizontal: 24), + Styles.textStyle: const TextStyle( + color: Colors.black, + fontSize: 15, + height: 1.4, + ), + }; + }),], + inlineTextStyler: defaultInlineTextStyler) + ), + ) + ])), + ), + ); + } +} + +class OverlayService { + static final OverlayService _instance = OverlayService._internal(); + factory OverlayService() => _instance; + OverlayService._internal(); + OverlayEntry? _overlayEntry; + + void showPersistentWidget(BuildContext context) { + if (_overlayEntry != null) { + print("overlay visible"); + return; + } + _overlayEntry = OverlayEntry( + builder: (context) => ComposeEmail( + onClose: () { + removeComposeWidget(); + }, + onMinimize: (String content) { + minimizeComposeWidget(content); + }, + onSendMessage: (message) { + print('msg senf form overlay $message'); + }, + )); + Navigator.of(context).overlay?.insert(_overlayEntry!); + print("inserted into tree"); + } + + void removeComposeWidget() { + _overlayEntry?.remove(); + _overlayEntry = null; + } + + String minimizeComposeWidget(String content) { + //just hide the overlay but keep its info + return ''; + } +} From dda581bda0796ef1ab22ca0715674ba13ab9e33d Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 22 Jul 2025 23:11:05 -0400 Subject: [PATCH 39/64] super editor and its md plugin --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index fd454e1..e1ef19b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: markdown_widget: ^2.3.2+8 markdown: ^7.3.0 go_router: ^16.0.0 + super_editor: ^0.3.0-dev.27 + super_editor_markdown: 0.1.8 dev_dependencies: flutter_test: From 07091eb708f324b918a819fda40da6d28df4b7c3 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 22 Jul 2025 23:12:34 -0400 Subject: [PATCH 40/64] data structure for augment capabilities --- lib/structs.dart | 100 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/lib/structs.dart b/lib/structs.dart index e99c0d2..6f0f0e7 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -1,6 +1,7 @@ //data structures import 'dart:typed_data'; +import 'package:markdown/markdown.dart' as md; class GetThreadResponse { final int id; @@ -157,6 +158,94 @@ class AugmentTree { AugmentTree? parent; String ogTag = ''; String numbering = ''; + Map hirarchyDict = { + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4, + "h5": 5, + "h6": 6, + "p": 8, + "ul": 8, + "li": 8, + }; + + AugmentTree(); + + AugmentTree.fromMD(String rawMD) { + //makes raw MD into an augmentTree + print("started markdown2tree"); + final List nakedList = md.Document().parseLines(rawMD.split( + '\n')); //emails md is the index of the email in the thread, since this only handles one thus it shall be removed + // AugmentTree zoomTreeRoot = AugmentTree(); + for (var node in nakedList) { + //maybe do an add function, but isn't this it? + if (node is md.Element) { + AugmentTree temp = AugmentTree(); + temp.data = node.textContent; + temp.ogTag = node.tag; + if (hirarchyDict.containsKey(node.tag)) { + // make this O(1) + _add2Tree(this, node); + // print(node); + } + } + } + this.addNumbering(); + } + + void _add2Tree(AugmentTree tree, md.Element node2add) { + // adds node to its corresponding place + AugmentTree newNode = AugmentTree(); + newNode.setData(node2add.textContent); + newNode.ogTag = node2add.tag; + // cases, + //1. a node that comes is lower than the root.children last, if so it goes beneath it + if (tree.children.isEmpty) { + // new level to be created when totally empty + tree.children.add(newNode); + newNode.parent = tree; + } else if (tree.children.isNotEmpty && + tree.children.last.ogTag.isNotEmpty) { + if ((hirarchyDict[node2add.tag] ?? + -1) < // e.g. new node is h1 and old is h2, heapify + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + //have to figure out the borthers + //assuming it all goes right + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + return; + } else if (tree.children.last.parent == null) { + // becomes the new top level + for (AugmentTree brother in tree.children) { + brother.parent = newNode; + } + tree.children = [newNode]; + } else { + newNode.parent = tree; + tree.children.add(newNode); + } + } else if ((hirarchyDict[node2add.tag] ?? + -1) > // go down e.g. new node is h3 and old is h2 or something + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + print("-1 ${tree.children.last.ogTag}"); + return; + } + + _add2Tree(tree.children.last, node2add); + } else if ((hirarchyDict[node2add.tag] ?? -1) == + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + tree.children.add(newNode); + newNode.parent = tree; + } + } + } void setData(String data) { this.data = data; @@ -176,7 +265,7 @@ class AugmentTree { parentIsLettered = false; } else { parentIsLettered = prefix.runes.last >= 'a'.runes.first && - prefix.runes.last <= 'z'.runes.first; + prefix.runes.last <= 'z'.runes.first; } if (prefix.isEmpty) { @@ -197,14 +286,19 @@ class AugmentTree { } } +//perhaps make a struct that builds augment tree, since its so complex and needs to be like recursive -class MarkdownParsed{ +class MarkdownParsed { + //struct for holding the MD given in endpoint //not used final String text; MarkdownParsed({required this.text}); - factory MarkdownParsed.fromJson(Map json){ + factory MarkdownParsed.fromJson(Map json) { return MarkdownParsed( text: json['md'] ?? '', ); } } +//should make an md to tree class/struct + +// make a for loop of rows with markdown From 214a60ce1b3df690989b1cd4f30ed1f26eb74e54 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Jul 2025 10:28:50 -0400 Subject: [PATCH 41/64] change in the parameters --- lib/SonicEmailViewWeb.dart | 1 + lib/collapsableEmailsAndroid.dart | 5 +++++ lib/collapsableEmailsStub.dart | 11 ++++++++++- lib/emailViewAndroid.dart | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/SonicEmailViewWeb.dart b/lib/SonicEmailViewWeb.dart index c1d6881..53ab936 100644 --- a/lib/SonicEmailViewWeb.dart +++ b/lib/SonicEmailViewWeb.dart @@ -101,6 +101,7 @@ class _SonicEmailViewState extends State { onJumpToNumbering: _scrollToNumber, onViewspecs: _handleViewspecs, onFiltering: _handleFiltering, + emails: [widget.email.name], subject: '', rootAugment: null, ), Row( // title of email diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart index ae432e8..275eb6f 100644 --- a/lib/collapsableEmailsAndroid.dart +++ b/lib/collapsableEmailsAndroid.dart @@ -90,6 +90,11 @@ class _CollapsableEmailsState extends State { super.dispose(); } + + List getThreads() { + return widget.thread; + } + void _add2Tree(AugmentTree tree, md.Element node2add) { // adds node to its corresponding place AugmentTree newNode = AugmentTree(); diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart index b523f71..e4ec443 100644 --- a/lib/collapsableEmailsStub.dart +++ b/lib/collapsableEmailsStub.dart @@ -9,13 +9,22 @@ class CollapsableEmails extends StatefulWidget { CollapsableEmails( {required this.thread, required this.threadMarkdown, - required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs, String? targetFiltering}); + required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs, String? targetFiltering, required String nameOfDocument}); + + get getThreads => null; + + get getAugmentRoot => null; @override State createState() => _CollapsableEmailsState(); } class _CollapsableEmailsState extends State { + + List getThreads() { + return widget.thread; + } + @override Widget build(BuildContext context) { return Scaffold(body: Text("collapsable stud")); diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart index 9f81432..5ef2efd 100644 --- a/lib/emailViewAndroid.dart +++ b/lib/emailViewAndroid.dart @@ -66,6 +66,7 @@ class _EmailViewState extends State { onJumpToNumbering: _scrollToNumber, onViewspecs: _viewSpecs, onFiltering: _filteringQuery, + emails: widget.messages, subject: '', rootAugment: null, ), Row( children: [ From c025fbe07a191d2fcecc1b4054fe630ede896c25 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Jul 2025 10:29:13 -0400 Subject: [PATCH 42/64] add the compose widget to sidebar --- lib/home_page.dart | 136 +++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index cbd2e76..1b277ce 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -5,6 +5,7 @@ import 'structs.dart'; import 'api_service.dart'; import 'package:flutter/material.dart'; import 'email.dart'; +import 'Compose.dart'; class HomeScreen extends StatefulWidget { @override @@ -44,49 +45,50 @@ class _HomeScreenState extends State with TickerProviderStateMixin { }); } - void _showOptionsSearchDialog () async { + void _showOptionsSearchDialog() async { List folders = await apiService.fetchFolders(); if (mounted) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Choose an Option'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: folders.map((option) { - return ListTile( - title: Text(option), - leading: Radio( - value: option, - groupValue: _selectedOption, // Bind with _selectedOption - onChanged: (String? value) { - setState(() { - _selectedOption = value; - }); - Navigator.of(context).pop(); // Close the dialog on selection - }, - ), - ); - }).toList(), - ), - actions: [ - ElevatedButton( - child: Text('Submit'), - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('You selected: $_selectedOption'), - )); - }, + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Choose an Option'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: folders.map((option) { + return ListTile( + title: Text(option), + leading: Radio( + value: option, + groupValue: _selectedOption, // Bind with _selectedOption + onChanged: (String? value) { + setState(() { + _selectedOption = value; + }); + Navigator.of(context) + .pop(); // Close the dialog on selection + }, + ), + ); + }).toList(), ), - ], - ); - }, - );} + actions: [ + ElevatedButton( + child: Text('Submit'), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('You selected: $_selectedOption'), + )); + }, + ), + ], + ); + }, + ); + } } - // Remove a tab void _removeTab(int index) { @@ -119,7 +121,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { body: ListView.separated( itemCount: result.length, itemBuilder: (context, index) { - final SerializableMessage email = result[index]; + final SerializableMessage email = result[index]; return ListTile( title: Text(email.from, style: TextStyle(fontWeight: FontWeight.bold)), @@ -132,27 +134,24 @@ class _HomeScreenState extends State with TickerProviderStateMixin { // print('tapped'); // List emailContent = // await apiService.fetchEmailContent([email.id], email.list); - //call the foldable - + //call the foldable + List emailContent = // list of the html - await apiService.fetchEmailContent([email.id], email.list); + await apiService + .fetchEmailContent([email.id], email.list); // List emailIds = email.messages; - - + Navigator.push( context, MaterialPageRoute( - builder: (context) =>SonicEmailView( - email: email, - emailHTML: emailContent[0]) - ), + builder: (context) => SonicEmailView( + email: email, emailHTML: emailContent[0])), ); }, ); }, separatorBuilder: (context, index) => Divider(), ), - ); } }, @@ -170,7 +169,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { return Scaffold( backgroundColor: Theme.of(context).colorScheme.onPrimary, body: Padding( - padding: const EdgeInsets.fromLTRB(0, 20, 0 , 20), + padding: const EdgeInsets.fromLTRB(0, 20, 0, 20), child: Scaffold( key: _scaffoldKey, drawer: FolderDrawer( @@ -195,6 +194,12 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + ListTile( + leading: Icon(Icons.edit_note_sharp), + onTap: () { + OverlayService() + .showPersistentWidget(context); + }), ListTile( leading: Icon(Icons.home), onTap: () { @@ -219,7 +224,8 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: Align( alignment: Alignment.bottomLeft, child: IconButton( - icon: Icon(Icons.close, color: Colors.white), + icon: + Icon(Icons.close, color: Colors.white), onPressed: () { setState(() { _isSidebarOpen = false; @@ -256,7 +262,8 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ), onSubmitted: (value) { if (value.isNotEmpty) { - _performSearch(value, _selectedOption); + _performSearch( + value, _selectedOption); } //this is the input box i mentioned // if (value == '') { @@ -314,8 +321,10 @@ class _HomeScreenState extends State with TickerProviderStateMixin { Text(entry.value), if (entry.value != 'Emails') GestureDetector( - onTap: () => _removeTab(entry.key), - child: Icon(Icons.close, size: 16), + onTap: () => + _removeTab(entry.key), + child: Icon(Icons.close, + size: 16), ), ], ), @@ -333,23 +342,28 @@ class _HomeScreenState extends State with TickerProviderStateMixin { children: [ ElevatedButton( onPressed: () { - _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState - ?.updatePagenation('back'); + _emailPageKey.currentState!.isBackDisabled + ? null + : _emailPageKey.currentState + ?.updatePagenation('back'); }, child: Icon(Icons.navigate_before), ), Builder( builder: (context) { - final emailState = _emailPageKey.currentState; + final emailState = + _emailPageKey.currentState; if (emailState == null) { // Schedule a rebuild once the state is available Future.microtask(() => setState(() {})); return Text('Loading...'); } - + return ValueListenableBuilder( - valueListenable: emailState.currentPageNotifier, - builder: (context, value, _) => Text('$value'), + valueListenable: + emailState.currentPageNotifier, + builder: (context, value, _) => + Text('$value'), ); }, ), @@ -376,7 +390,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { }).toList(), ), ), - + // if (_tabs.isEmpty) // Expanded( // child: EmailPage(key: _emailPageKey), From b961be3e8bf0f6630bcb0400bf5a29b62236557a Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Jul 2025 10:29:28 -0400 Subject: [PATCH 43/64] routing, and link handling --- lib/routingHandler.dart | 94 ++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/lib/routingHandler.dart b/lib/routingHandler.dart index f3052a6..55c7b26 100644 --- a/lib/routingHandler.dart +++ b/lib/routingHandler.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:markdown/markdown.dart' as md; import 'package:markdown_widget/markdown_widget.dart'; import 'api_service.dart'; +import 'structs.dart'; class Routinghandler extends StatefulWidget { Routinghandler(String link, emailID) { @@ -133,6 +134,7 @@ class Routinghandler extends StatefulWidget { class _RoutingHandlerState extends State { List markdownContent = []; bool _isLoaded = false; + AugmentTree? aug; @override void initState() { @@ -143,13 +145,15 @@ class _RoutingHandlerState extends State { Future _loadMarkdown() async { String folder = ApiService.currFolder; - // print(folder); print(widget.getEmailID()); String emailID = widget.emailID; print("inside _loadMarkdown in routinghandler $emailID"); markdownContent = - await ApiService().fetchMarkdownContent([emailID], "INBOX"); + await ApiService().fetchMarkdownContent([emailID], folder); // print(markdownContent); + aug = AugmentTree.fromMD(markdownContent[0]); + aug!.addNumbering(); + setState(() { _isLoaded = true; }); @@ -162,44 +166,58 @@ class _RoutingHandlerState extends State { child: CircularProgressIndicator(), ); } - return Scaffold( - appBar: AppBar( - title: Text("Routing Handler"), - leading: IconButton( - onPressed: () { - GoRouter.of(context).go('/home'); - }, - icon: const Icon(Icons.arrow_back_ios)), - ), - body: ConstrainedBox( - constraints: BoxConstraints( - minHeight: 100, - maxHeight: MediaQuery.of(context).size.height * 0.7, - ), - child: SingleChildScrollView( - child:MarkdownBlock(data: markdownContent[0]))), - ); - } -} - -class LinkViewer extends StatefulWidget { - const LinkViewer({super.key}); - - @override - State createState() => _LinkViewerState(); -} - -class _LinkViewerState extends State { - @override - Widget build(BuildContext context) { - // this should be a class that opens a popup of the email on the view it wants - return Scaffold( appBar: AppBar( - title: Text('url viewer'), + title: Text("Routing Handler"), + leading: IconButton( + onPressed: () { + GoRouter.of(context).go('/home'); + }, + icon: const Icon(Icons.arrow_back_ios)), ), - body: Column( - children: [], - )); + body: ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100, + maxHeight: MediaQuery.of(context).size.height * 0.7, + ), + child: SingleChildScrollView( + //inside here put the bunch rows + //make rows of markdownBlocks, but firstly i need to conveert the content into a tree + // child:MarkdownBlock(data: markdownContent[0]) + + child: Column(children: [ + for (int i = 0; i < this.aug!.children![0]!.children.length; i++) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // if (leftNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), + child: Text( + aug!.children![0]!.children![i]!.numbering, + style: TextStyle( + color: Color(Colors.purple[400]!.value)), + ), + ), + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Wrap(children: [ + MarkdownBlock( + data: aug!.children![0]!.children![i]!.data ?? ''), + ],) + ) + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), + child: Text( + aug!.children![0]!.children![i]!.numbering, + style: TextStyle( + color: Color(Colors.purple[400]!.value)), + ), + ), + ]), + ])))); } } From 71707bd1c0ea5a417d20d05c9039de3a1f40ef14 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Jul 2025 10:30:07 -0400 Subject: [PATCH 44/64] fix for composing widget --- lib/login.dart | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/login.dart b/lib/login.dart index ca2c3f9..a03f4d1 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -1,15 +1,22 @@ import 'dart:convert'; // import 'package:crab_ui/api_service.dart'; +import 'package:go_router/go_router.dart'; + import 'api_service.dart'; -import 'home_page.dart'; +// import 'home_page.dart'; import 'package:flutter/material.dart'; // import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:http/http.dart' as http; // import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/services.dart' show rootBundle; -class AuthService { +class AuthService extends ChangeNotifier { Future isUserLoggedIn() async { + ApiService.ip = '192.168.2.38'; + ApiService.port = '3001'; + print("setted up"); + + return true; try { final response = await http.get(Uri.http('localhost:6823', 'read-config')); @@ -83,6 +90,7 @@ class LoginPage extends StatefulWidget { } class SplashScreen extends StatefulWidget { + //entry point @override _SplashScreenState createState() => _SplashScreenState(); } @@ -92,19 +100,24 @@ class _SplashScreenState extends State { @override void initState() { super.initState(); - _checkLoginStatus(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkLoginStatus(); + }); } Future _checkLoginStatus() async { // SharedPreferences prefs = await SharedPreferences.getInstance(); // print(prefs); // bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false; + await Future.delayed(const Duration(seconds: 1)); bool isLoggedIn = await _authService.isUserLoggedIn(); - print("is loogeed in $isLoggedIn"); + print("is logged in $isLoggedIn"); if (isLoggedIn) { - Navigator.pushReplacementNamed(context, '/home'); + context.go("/home"); + // Navigator.pushReplacementNamed(context, '/home'); } else { - Navigator.pushReplacementNamed(context, '/login'); + context.go("/login"); + // Navigator.pushReplacementNamed(context, '/login'); } } @@ -112,7 +125,7 @@ class _SplashScreenState extends State { Widget build(BuildContext context) { return Center( child: Scaffold( - body: Center(child: CircularProgressIndicator()), + body: Center(child: CircularProgressIndicator()), //nothing happens ), ); } @@ -132,6 +145,7 @@ class _LoginPageState extends State { final _formKey = GlobalKey(); Future setIp(String ip) async { + //this is not done :sob: :skull: // _configManager.setField("api_addr", ip); return false; } From 5d4854901edfbd1075ccb3d1da52103767c6e703 Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 9 Aug 2025 00:11:29 -0400 Subject: [PATCH 45/64] select emails in bulk and each, select all and unselect --- lib/email.dart | 181 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 136 insertions(+), 45 deletions(-) diff --git a/lib/email.dart b/lib/email.dart index f922d61..2c2a260 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -3,53 +3,137 @@ import 'api_service.dart'; import 'structs.dart'; import 'emailView.dart'; -class EmailListScreen extends StatelessWidget { +class EmailListScreen extends StatefulWidget { final List emails; final Future> Function(List, String) getEmailContent; final String folder; + final GlobalKey<_EmailListScreenState> key; - EmailListScreen( - {required this.emails, + EmailListScreen({ + required this.key, + required this.emails, required this.getEmailContent, - required this.folder}); -//fix the email list + required this.folder}) + : super(key: key); + + @override + _EmailListScreenState createState() => _EmailListScreenState(); +} + +class _EmailListScreenState extends State { + late List selectStates; // for checkboxes if its selected or not + late List selectedEmails = + []; // holds the emails that are selected i.e. the emails that got the checkbox on + final Set _hoveredRows = {}; //the row that is being hovered over atm + + @override + void initState() { + super.initState(); + selectStates = List.filled(widget.emails.length, false); + } + + @override + void didUpdateWidget(covariant EmailListScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.emails.length != widget.emails.length) { + selectStates = List.filled(widget.emails.length, false); + } + } + + bool selectAllChecks(bool selectionType) { + setState(() { + for (int email = 0; email < selectStates.length; email++) { + selectStates[email] = selectionType; + } + }); + printTheSelected(); + return false; + } + + void printTheSelected() { + for (int i = 0; i < selectedEmails.length; i++) { + print(selectedEmails); + } + } + @override Widget build(BuildContext context) { return Scaffold( body: ListView.separated( - itemCount: emails.length, + itemCount: widget.emails.length, itemBuilder: (context, index) { - final email = emails[index]; - return ListTile( - title: Text(email.from_name, - style: TextStyle(fontWeight: FontWeight.bold)), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [Text(email.subject)], - ), - trailing: Text(email.date.toString()), - onTap: () async { - List emailContent = // list of the html - await getEmailContent(email.messages, folder); + final email = widget.emails[index]; - print(email.messages); //email ids of the thread - Navigator.push( - context, - MaterialPageRoute( - // could call collapsable and inside collable each calls email view? - builder: (context) => EmailView( - emailContent: emailContent, - from: email.from_address, - name: email.from_name, - to: email.to.toString(), - subject: email.subject, - date: email.date.toString(), - id: email.id.toString(), //i think this is thread id? - messages: email.messages, - ), - ), - ); - }, + return MouseRegion( + onEnter: (_) => setState(() => _hoveredRows.add(index)), + onExit: (_) => setState(() => _hoveredRows.remove(index)), + child: ListTile( + leading: Checkbox( + value: selectStates[index], + onChanged: (bool? value) { + setState(() { + //works great + selectStates[index] = value ?? false; + if (value!) { + selectedEmails.add(widget.emails[index]); + } else { + selectedEmails.remove(widget.emails[index]); + } + }); + }, + ), + title: Text(email.from_name, + style: TextStyle(fontWeight: FontWeight.bold)), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text(email.subject)], + ), + trailing: _hoveredRows.contains(index) + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.mark_email_read_outlined), + onPressed: () { + //mark email as read + }, + ), + IconButton( + icon: Icon(Icons.delete_outline), + onPressed: () { + //delete email + }, + ), + ], + ) + : Text(email.date.toString()), + onTap: () async { + List emailContent = // list of the html + await widget.getEmailContent(email.messages, widget.folder); + + print(email.messages); //email ids of the thread + if (widget.folder == "Drafts") { + print("IN DRAFTS MOVE THE CONTENT TO THE WRITING THING"); + } else { + Navigator.push( + context, + MaterialPageRoute( + // could call collapsable and inside collable each calls email view? + builder: (context) => EmailView( + emailContent: emailContent, + from: email.from_address, + name: email.from_name, + to: email.to.toString(), + subject: email.subject, + date: email.date.toString(), + id: email.id.toString(), //i think this is thread id? + messages: email.messages, + ), + ), + ); + } + }, + ), ); }, separatorBuilder: (context, index) => Divider(), @@ -75,6 +159,9 @@ class EmailPageState extends State { ValueNotifier currentPageNotifier = ValueNotifier(1); int page = 1; bool isBackDisabled = false; + + final GlobalKey<_EmailListScreenState> emailListKey = GlobalKey<_EmailListScreenState>(); + @override void initState() { @@ -84,6 +171,7 @@ class EmailPageState extends State { _fetchEmails(); } + List get getEmails => emails; String getPage() => widget.page.toString(); bool get backDisabled => isBackDisabled; @@ -114,7 +202,6 @@ class EmailPageState extends State { } }); } - // print(currentPage); print(widget.page); _fetchEmails(); } @@ -133,15 +220,19 @@ class EmailPageState extends State { } } + bool selectAllEmails(bool selectionType) { + emailListKey.currentState?.selectAllChecks(selectionType); + return false; + } + @override Widget build(BuildContext context) { - return Scaffold( - body: EmailListScreen( - emails: emails, - // getEmailContent: apiService.fetchEmailContent, - getEmailContent: apiService.fetchMarkdownContent, - folder: widget.selectedFolder, //try to grab from it directly - ), - ); + return Scaffold(body: EmailListScreen( + key: emailListKey, + emails: emails, + // getEmailContent: apiService.fetchEmailContent, + getEmailContent: apiService.fetchMarkdownContent, + folder: widget.selectedFolder, //try to grab from it directly + )); } } From 344029d0dda0738c7f0829679dc47a44be2f41e7 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 11 Aug 2025 17:34:59 -0400 Subject: [PATCH 46/64] color when hoverred, and seen updated --- lib/email.dart | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/email.dart b/lib/email.dart index 2c2a260..7bf36a6 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -9,8 +9,8 @@ class EmailListScreen extends StatefulWidget { final String folder; final GlobalKey<_EmailListScreenState> key; - EmailListScreen({ - required this.key, + EmailListScreen( + {required this.key, required this.emails, required this.getEmailContent, required this.folder}) @@ -25,6 +25,7 @@ class _EmailListScreenState extends State { late List selectedEmails = []; // holds the emails that are selected i.e. the emails that got the checkbox on final Set _hoveredRows = {}; //the row that is being hovered over atm + bool bulkSelectMenu = false; @override void initState() { @@ -42,9 +43,12 @@ class _EmailListScreenState extends State { bool selectAllChecks(bool selectionType) { setState(() { - for (int email = 0; email < selectStates.length; email++) { - selectStates[email] = selectionType; - } + if (selectionType) { + bulkSelectMenu = true; + } + for (int email = 0; email < selectStates.length; email++) { + selectStates[email] = selectionType; + } }); printTheSelected(); return false; @@ -62,8 +66,13 @@ class _EmailListScreenState extends State { body: ListView.separated( itemCount: widget.emails.length, itemBuilder: (context, index) { + Color seenColour; final email = widget.emails[index]; - + if (email.seen) { + seenColour = ThemeData().highlightColor; + } else { + seenColour = Colors.transparent; + } return MouseRegion( onEnter: (_) => setState(() => _hoveredRows.add(index)), onExit: (_) => setState(() => _hoveredRows.remove(index)), @@ -88,6 +97,7 @@ class _EmailListScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [Text(email.subject)], ), + // tileColor: () , trailing: _hoveredRows.contains(index) ? Row( mainAxisSize: MainAxisSize.min, @@ -107,10 +117,12 @@ class _EmailListScreenState extends State { ], ) : Text(email.date.toString()), + hoverColor: Colors.transparent, + tileColor: seenColour, onTap: () async { List emailContent = // list of the html await widget.getEmailContent(email.messages, widget.folder); - + // print("thread id? $email.id"); yes print(email.messages); //email ids of the thread if (widget.folder == "Drafts") { print("IN DRAFTS MOVE THE CONTENT TO THE WRITING THING"); @@ -131,6 +143,7 @@ class _EmailListScreenState extends State { ), ), ); + ApiService().markAsSeen(email.id); } }, ), @@ -159,9 +172,9 @@ class EmailPageState extends State { ValueNotifier currentPageNotifier = ValueNotifier(1); int page = 1; bool isBackDisabled = false; - - final GlobalKey<_EmailListScreenState> emailListKey = GlobalKey<_EmailListScreenState>(); - + + final GlobalKey<_EmailListScreenState> emailListKey = + GlobalKey<_EmailListScreenState>(); @override void initState() { @@ -227,7 +240,8 @@ class EmailPageState extends State { @override Widget build(BuildContext context) { - return Scaffold(body: EmailListScreen( + return Scaffold( + body: EmailListScreen( key: emailListKey, emails: emails, // getEmailContent: apiService.fetchEmailContent, From 1c6d3d6920ec6ac2645fe62442f5058fea092fd5 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 11 Aug 2025 17:35:29 -0400 Subject: [PATCH 47/64] menu's for selecting emails --- lib/home_page.dart | 147 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/lib/home_page.dart b/lib/home_page.dart index 1b277ce..ea472c7 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -19,6 +19,16 @@ class _HomeScreenState extends State with TickerProviderStateMixin { bool _isSidebarOpen = true; bool querySearches = false; String? _selectedOption = "INBOX"; + List _checkBulk = [ + "All", + "None", + "Read", + "Unread", + "Starred", + "Unstarred" + ]; + bool _checkboxState = false; + bool bulkOptionsState = false; List _tabs = ['Emails']; Map _tabWidgets = {}; @@ -307,6 +317,143 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ], ), ), + Container( + padding: EdgeInsets.fromLTRB(0, 4, 0, 4), + color: Theme.of(context).colorScheme.onPrimary, + child: Row( + children: [ + Padding( + padding: + const EdgeInsets.fromLTRB(4, 0, 0, 0), + child: Checkbox( + value: _checkboxState, + onChanged: (value) { + setState(() { + _checkboxState = !_checkboxState; + }); + if (_checkboxState) { + // var a = _tabWidgets["Emails"].; + print(_emailPageKey.currentState! + .selectAllEmails( + true)); //now i got them all but how do i go down to select them all? + print("all"); + bulkOptionsState = true; + } else { + _emailPageKey.currentState! + .selectAllEmails(false); + bulkOptionsState = false; + print("none"); + } + }), + ), + const SizedBox( + width: 0, + ), + PopupMenuButton( + icon: const Icon( + Icons.arrow_drop_down_outlined), + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + child: Text("All")), + const PopupMenuItem( + child: Text("None")), + const PopupMenuItem( + child: Text("Read")), + const PopupMenuItem( + child: Text("Unread")), + const PopupMenuItem( + child: Text("Starred")), + const PopupMenuItem( + child: Text("Unstarred")), + ], + onSelected: (String result) { + print("result $result"); + }, + ), + if (bulkOptionsState) ...[ + IconButton( + onPressed: null, + icon: Icon(Icons.archive_outlined)), + IconButton( + onPressed: null, + icon: Icon(Icons.delete_outlined)), + IconButton( + onPressed: null, + icon: Icon( + Icons.mark_email_read_outlined)), + IconButton( + onPressed: null, + icon: Icon( + Icons.drive_file_move_outlined)), + ], + PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (BuildContext context) { + if (!bulkOptionsState) { + return >[ + const PopupMenuItem( + child: Row( + children: [ + Icon(Icons + .mark_email_read_outlined), + const SizedBox( + width: 4.0, + ), + Text("Mark all as read") + ], + ), + ), + const PopupMenuDivider(), + PopupMenuItem( + child: Text( + "Select messages to see more actions", + style: TextStyle( + color: Colors + .blueGrey.shade300), + )) + ]; + } else { + return >[ + const PopupMenuItem( + child: Row( + children: [ + Icon(Icons + .mark_email_unread_outlined), + const SizedBox( + width: 4.0, + ), + Text("Mark as unread") + ], + ), + ), + const PopupMenuItem( + child: Row( + children: [ + Icon(Icons.snooze_outlined), + const SizedBox( + width: 4.0, + ), + Text("Snooze") + ], + ), + ), + const PopupMenuItem( + child: Row( + children: [ + Icon(Icons.star_border_outlined), + const SizedBox( + width: 4.0, + ), + Text("Add star") + ], + ), + ), + ]; + } + }), + ], + )), Container( color: Color.fromARGB(255, 131, 110, 143), child: TabBar( From b8987e6a8da269ccf53e67efd3c88d023bbdcb34 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 11 Aug 2025 17:35:39 -0400 Subject: [PATCH 48/64] update struct --- lib/structs.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/structs.dart b/lib/structs.dart index 6f0f0e7..aee3358 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -11,6 +11,7 @@ class GetThreadResponse { final String from_name; final String from_address; final List to; + final bool seen; GetThreadResponse({ required this.id, @@ -20,19 +21,20 @@ class GetThreadResponse { required this.from_name, required this.from_address, required this.to, + required this.seen, }); factory GetThreadResponse.fromJson(Map json) { var toList = json['to'] as List; return GetThreadResponse( - id: json['id'], - messages: List.from(json['messages']), - subject: json['subject'], - date: DateTime.parse(json['date']), - from_name: json['from_name'], - from_address: json['from_address'], - to: toList.map((i) => MailAddress.fromJson(i)).toList(), - ); + id: json['id'], + messages: List.from(json['messages']), + subject: json['subject'], + date: DateTime.parse(json['date']), + from_name: json['from_name'], + from_address: json['from_address'], + to: toList.map((i) => MailAddress.fromJson(i)).toList(), + seen: json['seen']); } } From 0874ffa98e718e72c01eb0636fb6604333661084 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 11 Aug 2025 18:01:40 -0400 Subject: [PATCH 49/64] seen repaint fixed --- lib/email.dart | 4 ++++ lib/structs.dart | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/email.dart b/lib/email.dart index 7bf36a6..296f61b 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -106,6 +106,10 @@ class _EmailListScreenState extends State { icon: Icon(Icons.mark_email_read_outlined), onPressed: () { //mark email as read + setState(() { + widget.emails[index].seen = true; + ApiService().markAsSeen(email.id); + }); }, ), IconButton( diff --git a/lib/structs.dart b/lib/structs.dart index aee3358..4b0a9ba 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -11,7 +11,7 @@ class GetThreadResponse { final String from_name; final String from_address; final List to; - final bool seen; + late bool seen; GetThreadResponse({ required this.id, From 0bfc869e74c4b7e31ff7118269794da4ecb85dd2 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 12 Aug 2025 14:28:24 -0400 Subject: [PATCH 50/64] markselectedAsRead, onSelectionChanged callback, adding tickerproviderStateMixin, fix bugs, mark as rad, true and false --- lib/email.dart | 99 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/lib/email.dart b/lib/email.dart index 296f61b..244194e 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:markdown/markdown.dart' as md; import 'api_service.dart'; import 'structs.dart'; import 'emailView.dart'; @@ -8,24 +9,28 @@ class EmailListScreen extends StatefulWidget { final Future> Function(List, String) getEmailContent; final String folder; final GlobalKey<_EmailListScreenState> key; + final Function(List)? onSelectionChanged; - EmailListScreen( - {required this.key, - required this.emails, - required this.getEmailContent, - required this.folder}) - : super(key: key); + EmailListScreen({ + required this.key, + required this.emails, + required this.getEmailContent, + required this.folder, + this.onSelectionChanged, + }) : super(key: key); @override _EmailListScreenState createState() => _EmailListScreenState(); } -class _EmailListScreenState extends State { +class _EmailListScreenState extends State + with TickerProviderStateMixin { late List selectStates; // for checkboxes if its selected or not late List selectedEmails = []; // holds the emails that are selected i.e. the emails that got the checkbox on final Set _hoveredRows = {}; //the row that is being hovered over atm bool bulkSelectMenu = false; + final GlobalKey _emailPageKey = GlobalKey(); @override void initState() { @@ -42,21 +47,57 @@ class _EmailListScreenState extends State { } bool selectAllChecks(bool selectionType) { + //perhaps it should return a list of the selected setState(() { + selectedEmails = []; if (selectionType) { bulkSelectMenu = true; - } - for (int email = 0; email < selectStates.length; email++) { - selectStates[email] = selectionType; + for (int email = 0; email < selectStates.length; email++) { + selectStates[email] = selectionType; + selectedEmails.add(widget.emails[email]); + } + } else { + for (int email = 0; email < selectStates.length; email++) { + selectStates[email] = selectionType; + } + selectedEmails = []; } }); + widget.onSelectionChanged?.call(selectedEmails); printTheSelected(); return false; } + bool markAsRead(bool read) { + print("markasread $read"); + setState(() { + if (read) { + //read + for (int email = 0; email < selectedEmails.length; email++) { + selectedEmails[email].seen = read; + ApiService() + .markAsSeen(selectedEmails[email].id); //the remote or .json + } + } else { + //unread + for (int email = 0; email < selectedEmails.length; email++) { + selectedEmails[email].seen = read; + ApiService() + .markAsUnseen(selectedEmails[email].id); //the remote or .json + print(selectedEmails[email].subject); + } + } + }); + return false; + } + + List listOfSelectedThreads() { + return selectedEmails; + } + void printTheSelected() { for (int i = 0; i < selectedEmails.length; i++) { - print(selectedEmails); + print(selectedEmails[i].subject); } } @@ -83,11 +124,19 @@ class _EmailListScreenState extends State { setState(() { //works great selectStates[index] = value ?? false; - if (value!) { - selectedEmails.add(widget.emails[index]); - } else { - selectedEmails.remove(widget.emails[index]); - } + + setState(() { + if (value!) { + selectedEmails.add(widget.emails[index]); + //here i must update the other side + _emailPageKey.currentState?.getListOfSelected(); + } else { + selectedEmails.remove(widget.emails[index]); + _emailPageKey.currentState?.getListOfSelected(); + } + widget.onSelectionChanged?.call(selectedEmails); + print(selectedEmails); + }); }); }, ), @@ -97,7 +146,6 @@ class _EmailListScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [Text(email.subject)], ), - // tileColor: () , trailing: _hoveredRows.contains(index) ? Row( mainAxisSize: MainAxisSize.min, @@ -161,10 +209,12 @@ class _EmailListScreenState extends State { // ignore: must_be_immutable class EmailPage extends StatefulWidget { - EmailPage({Key? key}) : super(key: key); String selectedFolder = "INBOX"; //starter int offset = 0; int page = 1; + final Function(List)? onSelectionChanged; + + EmailPage({Key? key, this.onSelectionChanged}) : super(key: key); @override EmailPageState createState() => EmailPageState(); @@ -239,9 +289,19 @@ class EmailPageState extends State { bool selectAllEmails(bool selectionType) { emailListKey.currentState?.selectAllChecks(selectionType); - return false; + return selectionType; } + bool markSelectedAsRead(bool selectionType) { + emailListKey.currentState?.markAsRead(selectionType); + return selectionType; + } + + List getListOfSelected() { + return emailListKey.currentState!.listOfSelectedThreads() ?? []; + } + // return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)]; + @override Widget build(BuildContext context) { return Scaffold( @@ -251,6 +311,7 @@ class EmailPageState extends State { // getEmailContent: apiService.fetchEmailContent, getEmailContent: apiService.fetchMarkdownContent, folder: widget.selectedFolder, //try to grab from it directly + onSelectionChanged: widget.onSelectionChanged, )); } } From b79d68c7a285583f84c378dc41243ee99e8f66f8 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 12 Aug 2025 14:29:10 -0400 Subject: [PATCH 51/64] mark as read, and fix bugs --- lib/home_page.dart | 126 +++++++++++++++++++++++++++++++-------------- 1 file changed, 87 insertions(+), 39 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index ea472c7..f4e67f7 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -29,6 +29,8 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ]; bool _checkboxState = false; bool bulkOptionsState = false; + List selectedThreads = + []; //this should store the emails that are being stored downstream too List _tabs = ['Emails']; Map _tabWidgets = {}; @@ -40,6 +42,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin { _tabController = TabController(length: _tabs.length, vsync: this); _tabWidgets['Emails'] = EmailPage( key: _emailPageKey, + onSelectionChanged: (updatedList) { + setState(() { + selectedThreads = updatedList; + }); + }, ); } @@ -354,24 +361,45 @@ class _HomeScreenState extends State with TickerProviderStateMixin { Icons.arrow_drop_down_outlined), itemBuilder: (BuildContext context) => >[ - const PopupMenuItem( - child: Text("All")), - const PopupMenuItem( - child: Text("None")), - const PopupMenuItem( - child: Text("Read")), - const PopupMenuItem( - child: Text("Unread")), - const PopupMenuItem( + PopupMenuItem( + //select all + child: Text("All"), + onTap: () { + _emailPageKey.currentState! + .selectAllEmails(true); + }, + ), + PopupMenuItem( + child: Text("None"), + onTap: () { + _emailPageKey.currentState! + .selectAllEmails(false); + }, + ), + PopupMenuItem( + child: Text("Read"), + onTap: () { + //select the read + }, + ), + PopupMenuItem( + //select the unread + child: Text("Unread"), + onTap: () { + //select the unread + }, + ), + PopupMenuItem( child: Text("Starred")), - const PopupMenuItem( + PopupMenuItem( child: Text("Unstarred")), ], onSelected: (String result) { print("result $result"); }, ), - if (bulkOptionsState) ...[ + if (selectedThreads.isNotEmpty) ...[ + //this needs to know if anything got selected, IconButton( onPressed: null, icon: Icon(Icons.archive_outlined)), @@ -379,7 +407,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin { onPressed: null, icon: Icon(Icons.delete_outlined)), IconButton( - onPressed: null, + onPressed: () { + _emailPageKey.currentState! + .markSelectedAsRead( + true); //mark as read + }, icon: Icon( Icons.mark_email_read_outlined)), IconButton( @@ -390,9 +422,10 @@ class _HomeScreenState extends State with TickerProviderStateMixin { PopupMenuButton( icon: const Icon(Icons.more_vert), itemBuilder: (BuildContext context) { - if (!bulkOptionsState) { + if (selectedThreads.isEmpty) { + //why tf? return >[ - const PopupMenuItem( + PopupMenuItem( child: Row( children: [ Icon(Icons @@ -403,19 +436,28 @@ class _HomeScreenState extends State with TickerProviderStateMixin { Text("Mark all as read") ], ), + onTap: () { + _emailPageKey.currentState! + .selectAllEmails(true); + _emailPageKey.currentState! + .markSelectedAsRead(true); + _emailPageKey.currentState! + .selectAllEmails(false); + }, ), const PopupMenuDivider(), PopupMenuItem( - child: Text( - "Select messages to see more actions", - style: TextStyle( - color: Colors - .blueGrey.shade300), - )) + child: Text( + "Select messages to see more actions", + style: TextStyle( + color: Colors + .blueGrey.shade300), + ), + ) ]; } else { return >[ - const PopupMenuItem( + PopupMenuItem( child: Row( children: [ Icon(Icons @@ -426,28 +468,34 @@ class _HomeScreenState extends State with TickerProviderStateMixin { Text("Mark as unread") ], ), + onTap: () { + _emailPageKey.currentState! + .markSelectedAsRead( + false); + }, ), const PopupMenuItem( - child: Row( - children: [ - Icon(Icons.snooze_outlined), - const SizedBox( - width: 4.0, - ), - Text("Snooze") - ], - ), + child: Row( + children: [ + Icon(Icons.snooze_outlined), + const SizedBox( + width: 4.0, + ), + Text("Snooze") + ], + ), ), const PopupMenuItem( - child: Row( - children: [ - Icon(Icons.star_border_outlined), - const SizedBox( - width: 4.0, - ), - Text("Add star") - ], - ), + child: Row( + children: [ + Icon(Icons + .star_border_outlined), + const SizedBox( + width: 4.0, + ), + Text("Add star") + ], + ), ), ]; } From ab0adf62e4332f91bf382ac8dbf4d254c4008d97 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 20 Aug 2025 13:50:22 -0400 Subject: [PATCH 52/64] added mark as seen or unseen api call and deleteEmail, which only moves it to a folder called Deleted Crabmail --- lib/api_service.dart | 72 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index ecd5a83..f40f93d 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -9,8 +9,8 @@ import 'package:http/http.dart' as http; import 'dart:convert'; class ApiService { - static String ip = ""; - static String port = ""; + static String ip = '127.0.0.1'; + static String port = "3001"; static List threadAttachments = []; //holds attachments of the thread static String currFolder = ""; @@ -169,7 +169,7 @@ class ApiService { Future moveEmail( //only moves the first email of the thread //or perhaps should do the last String fromFolder, - String thread_id, + String thread_id, //uid String toFolder) async { var url = Uri.http('$ip:$port', 'move_email'); @@ -185,7 +185,7 @@ class ApiService { Map requestBody = { 'from': fromFolder, 'uid': firstMail.uid.toString(), - 'to': toFolder, + 'to': "Deleted Crabmail", }; try { @@ -356,7 +356,7 @@ class ApiService { if (response.statusCode == 200) { counter += 1; Map json = jsonDecode(response.body); - + MDofThread.add(json['md'] ?? ''); try { List attachments = @@ -374,7 +374,67 @@ class ApiService { } catch (e) { print('_getMDContent caught error: $e'); } - + print("IDS inside fetch md content $IDsString"); + return MDofThread; } + + Future markAsSeen(int thread_id) async { + try { + var url = Uri.http( + '$ip:$port', 'post_seen_thread', {'id': thread_id.toString()}); + var response = await http.get(url); + if (response.statusCode == 200) { + var result = response.body; + print("data $result"); + } + } catch (e) { + print("markAsSeen failed $e"); + } + } + + Future markAsUnseen(int thread_id) async { + try { + var url = Uri.http( + '$ip:$port', 'post_unseen_thread', {'id': thread_id.toString()}); + var response = await http.get(url); + if (response.statusCode == 200) { + var result = response.body; + print("data $result"); + } + } catch (e) { + print("markAsUnseen failed $e"); + } + } + + Future deleteEmail(String from_folder, int thread_id) async { + // post + try { + List mailsInSerializable = + await this.threadsInSerializable(thread_id.toString()); + + if (mailsInSerializable.isEmpty) { + return false; + } + Map requestBody = {"from": from_folder, "uid": mailsInSerializable.first.uid.toString(), "to": "not used"}; + + //delete the email that is given to the + var url = Uri.http("$ip:$port", 'delete_email'); + var response = await http.post(url, + headers: { + "Content-Type": "application/json", + }, + body: jsonEncode(requestBody)); + if (response.statusCode == 200) { + print("response body: ${response.body}"); + return true; + } else { + print("not 200: ${response.body}"); + return false; + } + } catch (e) { + print("error in deleteEmail $e"); + return false; + } + } } From 5dc749eaeccc4046fcb49a102e9bd6c0a956eaec Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 20 Aug 2025 13:51:08 -0400 Subject: [PATCH 53/64] moves the selected threads of folder --- lib/email.dart | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/email.dart b/lib/email.dart index 244194e..dd5fee1 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -91,6 +91,20 @@ class _EmailListScreenState extends State return false; } + bool moveOfSelected(String destinyFolder) { + //this should be called from a widget + print("move of folder"); + setState(() { + for (int email = 0; email < selectedEmails.length; email++) { + ApiService().moveEmail( + widget.folder, selectedEmails[email].id.toString(), destinyFolder); + } + }); + return false; + } + + // Widget moveOfFolderWidget() + List listOfSelectedThreads() { return selectedEmails; } @@ -153,7 +167,7 @@ class _EmailListScreenState extends State IconButton( icon: Icon(Icons.mark_email_read_outlined), onPressed: () { - //mark email as read + // mark email as read setState(() { widget.emails[index].seen = true; ApiService().markAsSeen(email.id); @@ -163,7 +177,8 @@ class _EmailListScreenState extends State IconButton( icon: Icon(Icons.delete_outline), onPressed: () { - //delete email + // delete email + ApiService().deleteEmail(widget.folder, email.id); }, ), ], @@ -297,8 +312,13 @@ class EmailPageState extends State { return selectionType; } + bool moveSelectedOfFolder(String folder) { + emailListKey.currentState?.moveOfSelected(folder); + return false; + } + List getListOfSelected() { - return emailListKey.currentState!.listOfSelectedThreads() ?? []; + return emailListKey.currentState!.listOfSelectedThreads(); } // return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)]; From 9d05e612cccad2575490fa9f11553f78ad0a1a8f Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 20 Aug 2025 17:42:57 -0400 Subject: [PATCH 54/64] /send_email endpoint --- lib/api_service.dart | 47 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index f40f93d..4b7a087 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -411,12 +411,16 @@ class ApiService { // post try { List mailsInSerializable = - await this.threadsInSerializable(thread_id.toString()); + await this.threadsInSerializable(thread_id.toString()); if (mailsInSerializable.isEmpty) { return false; } - Map requestBody = {"from": from_folder, "uid": mailsInSerializable.first.uid.toString(), "to": "not used"}; + Map requestBody = { + "from": from_folder, + "uid": mailsInSerializable.first.uid.toString(), + "to": "not used" + }; //delete the email that is given to the var url = Uri.http("$ip:$port", 'delete_email'); @@ -437,4 +441,43 @@ class ApiService { return false; } } + + Future sendEmail( + String? to, String? subject, String? emailContent) async { + try { + var url = Uri.http('$ip:$port', 'send_email'); + + Map requestBody = { + "to": [to ?? ""], + "cc": [], + "bcc": [], + "subject": subject ?? "Untitled", + "in_reply_to": "", + "messages": [ + { + "message": emailContent ?? "", + "is_html": false}], + "attachments": [], + "inline_images": [], + }; + var response = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode(requestBody), + ); + if (response.statusCode == 200) { + print("response body: ${response.body}"); + } else { + print('error: ${response.statusCode}, response body: ${response.body}'); + return false; + } + + return true; + } catch (e) { + print("error in post send email $e"); + return false; + } + } } From 9d6ec2b6bcfc86207a2bc4b5bb9c090638cbf71d Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 21 Aug 2025 13:46:48 -0400 Subject: [PATCH 55/64] when in drafts if you click on an email listed there it will open the compose window with the info of that draft --- lib/Compose.dart | 185 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 139 insertions(+), 46 deletions(-) diff --git a/lib/Compose.dart b/lib/Compose.dart index 27081ee..43076f9 100644 --- a/lib/Compose.dart +++ b/lib/Compose.dart @@ -1,3 +1,5 @@ +import 'package:crab_ui/api_service.dart'; +import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'package:super_editor/super_editor.dart'; import 'package:super_editor_markdown/super_editor_markdown.dart'; @@ -6,12 +8,14 @@ class ComposeEmail extends StatefulWidget { final VoidCallback onClose; final Function(String) onMinimize; final Function(String) onSendMessage; - const ComposeEmail({ - Key? key, - required this.onMinimize, - required this.onClose, - required this.onSendMessage, - }) : super(key: key); + GetThreadResponse? emailDraftID; + ComposeEmail( + {Key? key, + required this.onMinimize, + required this.onClose, + required this.onSendMessage, + this.emailDraftID}) + : super(key: key); @override _ComposeEmailState createState() => _ComposeEmailState(); @@ -23,20 +27,38 @@ class _ComposeEmailState extends State { late final MutableDocument _document; late final MutableDocumentComposer _composer; TextEditingController _emailRecipientController = TextEditingController(); + TextEditingController _emailSubjectController = TextEditingController(); + List? contentOfDraft; @override void initState() { super.initState(); + _loadDraftContent(); + } - _document = MutableDocument(nodes: [ - ParagraphNode( - id: Editor.createNodeId(), - text: AttributedText("hello world!"), - ) - ]); - _composer = MutableDocumentComposer(); - _editor = - createDefaultDocumentEditor(document: _document!, composer: _composer!); + void _loadDraftContent() async { + if (widget.emailDraftID != null) { + String? drafted = widget.emailDraftID?.messages.last; + if (drafted != null) { + contentOfDraft = + await ApiService().fetchMarkdownContent([drafted!], "Drafts"); + setState(() { + _document = MutableDocument(nodes: [ + ParagraphNode( + id: Editor.createNodeId(), + text: AttributedText(contentOfDraft?[0] ?? + ""), // NOW THIS SHOULD BE WTV ITS IN DRAFTS + ) + ]); + _composer = MutableDocumentComposer(); + _editor = createDefaultDocumentEditor( + document: _document, composer: _composer); + _emailRecipientController.text = + widget.emailDraftID!.to[0].address; + _emailSubjectController.text = widget.emailDraftID!.subject; + }); + } + } } @override @@ -70,7 +92,7 @@ class _ComposeEmailState extends State { icon: Icon(Icons.minimize, color: Colors.grey[600])), IconButton( onPressed: () { - //TODO: implement that maximizing + //TODO: implement maximizing the window or widget }, icon: Icon(Icons.maximize, color: Colors.grey[600])), IconButton( @@ -81,9 +103,9 @@ class _ComposeEmailState extends State { ], ), Container( - // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS - width: 500.0, - height: 40.0, + // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS + // width: 500.0, + // height: 40.0, child: Row( children: [ TextButton(onPressed: () {}, child: Text("To:")), @@ -100,25 +122,95 @@ class _ComposeEmailState extends State { ], ), ), + SizedBox( + height: 4, + ), + Container( + // TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS + width: 500.0, + // height: 40.0, + child: Row( + children: [ + Expanded( + child: TextField( + controller: _emailSubjectController, + decoration: InputDecoration( + hintText: "Subject", + ), + ), + ) + ], + ), + ), Expanded( - //here the widget goes - child: SuperEditor( - editor: _editor!, - plugins: {MarkdownInlineUpstreamSyntaxPlugin()}, - stylesheet: Stylesheet( - rules: [StyleRule(BlockSelector.all, (doc, docNode) { - return { - Styles.maxWidth: 640.0, - Styles.padding: const CascadingPadding.symmetric(horizontal: 24), - Styles.textStyle: const TextStyle( - color: Colors.black, - fontSize: 15, - height: 1.4, + //here the widget goes + child: SuperEditor( + //make this its own + editor: _editor!, + plugins: {MarkdownInlineUpstreamSyntaxPlugin()}, + + // stylesheet: Stylesheet( + // rules: [StyleRule(BlockSelector.all, (doc, docNode) { + // return { + // Styles.maxWidth: 640.0, + // Styles.padding: const CascadingPadding.symmetric(horizontal: 24), + // Styles.textStyle: const TextStyle( + // color: Colors.black, + // // fontSize: 15, + // height: 1.4, + // ), + // }; + // }),], + // inlineTextStyler: defaultInlineTextStyler) + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: () { + print('sent'); + String markdown = + serializeDocumentToMarkdown(_editor.document); + + print(_emailRecipientController.text); + print(_emailSubjectController.text); + print(markdown); + ApiService().sendEmail(_emailRecipientController.text, + _emailSubjectController.text, markdown); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30)), + elevation: 4, + foregroundColor: Colors.white, + backgroundColor: Colors.blueAccent), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Send', + style: TextStyle(fontWeight: FontWeight.bold), ), - }; - }),], - inlineTextStyler: defaultInlineTextStyler) - ), + // const SizedBox( + // width: 8, + // ), + // Container( + // height: 30, width: 1.0, color: Colors.white), + // const SizedBox( + // width: 8, + // ), + // const Icon( + // Icons.arrow_drop_down, + // size: 24, + // ) + ], + ), + ), + ], + ), ) ])), ), @@ -131,6 +223,7 @@ class OverlayService { factory OverlayService() => _instance; OverlayService._internal(); OverlayEntry? _overlayEntry; + GetThreadResponse? draftID; void showPersistentWidget(BuildContext context) { if (_overlayEntry != null) { @@ -139,16 +232,16 @@ class OverlayService { } _overlayEntry = OverlayEntry( builder: (context) => ComposeEmail( - onClose: () { - removeComposeWidget(); - }, - onMinimize: (String content) { - minimizeComposeWidget(content); - }, - onSendMessage: (message) { - print('msg senf form overlay $message'); - }, - )); + onClose: () { + removeComposeWidget(); + }, + onMinimize: (String content) { + minimizeComposeWidget(content); + }, + onSendMessage: (message) { + print('msg senf form overlay $message'); + }, + emailDraftID: draftID)); Navigator.of(context).overlay?.insert(_overlayEntry!); print("inserted into tree"); } From 0e1e9a3cedc8b07b4743f6b7c4a8666055a19680 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 21 Aug 2025 13:49:23 -0400 Subject: [PATCH 56/64] onPressed for the move file when selected its checkbox --- lib/home_page.dart | 162 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index f4e67f7..5be194e 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -1,4 +1,5 @@ import 'package:crab_ui/sonicEmailView.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'folder_drawer.dart'; import 'structs.dart'; @@ -35,6 +36,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { List _tabs = ['Emails']; Map _tabWidgets = {}; TabController? _tabController; + OverlayEntry? _overlayEntry; @override void initState() { @@ -415,7 +417,163 @@ class _HomeScreenState extends State with TickerProviderStateMixin { icon: Icon( Icons.mark_email_read_outlined)), IconButton( - onPressed: null, + onPressed: () { + final overlay = Overlay.of(context); + String? + selectedFolder; // Variable to store the selected folder + + _overlayEntry = OverlayEntry( + builder: (context) => Stack( + children: [ + // Dimmed background + Container( + color: Colors.black54, + width: + MediaQuery.of(context) + .size + .width, + height: + MediaQuery.of(context) + .size + .height, + ), + // Focused content window + PointerInterceptor( + child: Center( + child: Material( + elevation: 8, + borderRadius: + BorderRadius + .circular(12), + child: ConstrainedBox( + constraints: + const BoxConstraints( + maxWidth: 400, + maxHeight: 500, + ), + child: Column( + mainAxisSize: + MainAxisSize + .min, + children: [ + Text( + 'Move email from folder ${ApiService.currFolder} to:', + style: TextStyle( + fontSize: + 16), + ), + Divider( + height: 1), + Expanded( + child: FutureBuilder< + List< + String>>( + future: ApiService() + .fetchFolders(), + builder: (context, + snapshot) { + if (snapshot + .connectionState == + ConnectionState + .waiting) { + return const Center( + child: + CircularProgressIndicator()); + } else if (snapshot + .hasError) { + return Center( + child: + Text('Error: ${snapshot.error}')); + } else if (snapshot + .hasData) { + return StatefulBuilder( + builder: + (context, + setState) { + return ListView( + shrinkWrap: + true, + children: + snapshot.data!.map((folder) { + return RadioListTile( + title: Text(folder), + value: folder, + groupValue: selectedFolder, + onChanged: (String? value) { + setState(() { + selectedFolder = value; // Update the selected folder + }); + }, + ); + }).toList(), + ); + }, + ); + } else { + return const Center( + child: + Text('No folders found.')); + } + }, + ), + ), + Divider( + height: 1), + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceEvenly, + children: [ + ElevatedButton( + onPressed: + () { + // Handle Accept button + if (selectedFolder != + null) { + print( + "Selected folder: $selectedFolder"); + // Store the selected folder or perform any action + // ApiService.currFolder = selectedFolder!; + _emailPageKey.currentState! //the one selected + .moveSelectedOfFolder(selectedFolder!); + _overlayEntry + ?.remove(); + } else { + print( + "No folder selected"); + } + }, + child: Text( + 'Accept'), + ), + ElevatedButton( + onPressed: + () { + // Handle Cancel button + _overlayEntry + ?.remove(); + }, + child: Text( + 'Cancel'), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + + if (_overlayEntry != null) { + overlay.insert(_overlayEntry!); + } + // _emailPageKey.currentState! //the one selected + // .moveSelectedOfFolder(); + }, icon: Icon( Icons.drive_file_move_outlined)), ], @@ -441,7 +599,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { .selectAllEmails(true); _emailPageKey.currentState! .markSelectedAsRead(true); - _emailPageKey.currentState! + _emailPageKey.currentState! .selectAllEmails(false); }, ), From 51c772a2a119852cf62f1f0b97b1af585c054fbf Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 21 Aug 2025 13:50:05 -0400 Subject: [PATCH 57/64] case where the folder is drafts, don't open the email but instead the compose widget --- lib/email.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/email.dart b/lib/email.dart index dd5fee1..926e753 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -3,6 +3,7 @@ import 'package:markdown/markdown.dart' as md; import 'api_service.dart'; import 'structs.dart'; import 'emailView.dart'; +import 'Compose.dart'; class EmailListScreen extends StatefulWidget { final List emails; @@ -193,6 +194,10 @@ class _EmailListScreenState extends State print(email.messages); //email ids of the thread if (widget.folder == "Drafts") { print("IN DRAFTS MOVE THE CONTENT TO THE WRITING THING"); + //open the compose + OverlayService _thisInstance = OverlayService(); + _thisInstance.draftID = email; + _thisInstance.showPersistentWidget(context); } else { Navigator.push( context, From de7758102bbd785e41b48ce19a0ba8ce79339987 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 21 Aug 2025 14:02:06 -0400 Subject: [PATCH 58/64] fixed slight yank --- lib/Compose.dart | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/Compose.dart b/lib/Compose.dart index 43076f9..331bed7 100644 --- a/lib/Compose.dart +++ b/lib/Compose.dart @@ -29,6 +29,7 @@ class _ComposeEmailState extends State { TextEditingController _emailRecipientController = TextEditingController(); TextEditingController _emailSubjectController = TextEditingController(); List? contentOfDraft; + bool isInitialized = false; @override void initState() { @@ -53,22 +54,43 @@ class _ComposeEmailState extends State { _composer = MutableDocumentComposer(); _editor = createDefaultDocumentEditor( document: _document, composer: _composer); - _emailRecipientController.text = - widget.emailDraftID!.to[0].address; + _emailRecipientController.text = widget.emailDraftID!.to[0].address; _emailSubjectController.text = widget.emailDraftID!.subject; + isInitialized = true; + }); } + } else { + setState(() { + _document = MutableDocument(nodes: [ + ParagraphNode( + id: Editor.createNodeId(), + text: AttributedText(""), + ), + ]); + _composer = MutableDocumentComposer(); + _editor = createDefaultDocumentEditor( + document: _document, composer: _composer); + isInitialized = true; + }); } } @override void dispose() { _editor.dispose(); + _emailRecipientController.dispose(); + _emailSubjectController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + if (!isInitialized) { + return Center( + child: CircularProgressIndicator(), + ); + } return Positioned( bottom: 10.0, right: 10.0, @@ -146,7 +168,7 @@ class _ComposeEmailState extends State { //here the widget goes child: SuperEditor( //make this its own - editor: _editor!, + editor: _editor, plugins: {MarkdownInlineUpstreamSyntaxPlugin()}, // stylesheet: Stylesheet( From 2ce8af1608c77567293a9eb235769d6431b06731 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 25 Aug 2025 13:54:18 -0400 Subject: [PATCH 59/64] fetch email reversed and, fixed moveEmail, so it moves every message and not just the first of the thread --- lib/api_service.dart | 60 +++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index 4b7a087..9c447a9 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -49,7 +49,39 @@ class ApiService { return []; } } + Future> fetchEmailsFromFolderReversed( + String folder, int pagenitaion) async { + try { + var url = Uri.http('$ip:$port', 'sorted_threads_by_date_current', { + 'folder': folder, + 'limit': '50', + 'offset': pagenitaion.toString(), + }); + + var response = await http.get(url); + List allEmails = []; + if (response.statusCode == 200) { + List json = jsonDecode(response.body); + for (var item in json) { + //each item in the json is a date + if (item.length > 1 && item[0] is String && item[1] is List) { + List threadIDs = List.from(item[1]); + for (var threadId in threadIDs) { + await fetchThreads(threadId, allEmails); + } + } + } + currFolder = folder; + return allEmails; + } else { + throw Exception('Failed to load threads'); + } + } catch (e) { + print('_displayEmailsFromFolder caught error: $e'); + return []; + } + } Future fetchThreads( //populates allEmails, which is the List that contains all the emails in a thread int threadId, @@ -180,16 +212,17 @@ class ApiService { return false; } - SerializableMessage firstMail = mailsInSerializable[0]; - - Map requestBody = { - 'from': fromFolder, - 'uid': firstMail.uid.toString(), - 'to': "Deleted Crabmail", - }; + // SerializableMessage firstMail = mailsInSerializable[0]; + try { - var response = await http.post( + for (SerializableMessage mail in mailsInSerializable) { + Map requestBody = { + 'from': fromFolder, + 'uid': mail.uid.toString(), + 'to': "Deleted Crabmail", + }; + var response = await http.post( url, headers: { 'Content-Type': 'application/json', @@ -201,7 +234,7 @@ class ApiService { return true; } else { print('error ${response.statusCode} ${response.body}'); - } + }} } catch (e) { print("failed trying to post move_email, with error: $e"); } @@ -374,8 +407,8 @@ class ApiService { } catch (e) { print('_getMDContent caught error: $e'); } - print("IDS inside fetch md content $IDsString"); - + // print("IDS inside fetch md content $IDsString"); + // print("inside apiservice $MDofThread"); return MDofThread; } @@ -454,9 +487,8 @@ class ApiService { "subject": subject ?? "Untitled", "in_reply_to": "", "messages": [ - { - "message": emailContent ?? "", - "is_html": false}], + {"message": emailContent ?? "", "is_html": false} + ], "attachments": [], "inline_images": [], }; From dea8f03fe193bd84578cdbe63e2d04a879d7f3de Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 25 Aug 2025 13:57:24 -0400 Subject: [PATCH 60/64] make the default the reversed --- lib/email.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/email.dart b/lib/email.dart index 926e753..05a1378 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -199,6 +199,7 @@ class _EmailListScreenState extends State _thisInstance.draftID = email; _thisInstance.showPersistentWidget(context); } else { + // print(email) Navigator.push( context, MaterialPageRoute( @@ -296,7 +297,7 @@ class EmailPageState extends State { void _fetchEmails() async { try { List fetchedEmails = await apiService - .fetchEmailsFromFolder(widget.selectedFolder, widget.offset); + .fetchEmailsFromFolderReversed(widget.selectedFolder, widget.offset); if (!mounted) return; setState(() { From 42c97a5f5d1200a5cc9a8d73681a74c7cabca74a Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 27 Aug 2025 15:08:14 -0400 Subject: [PATCH 61/64] fixed linking, now it works universally --- lib/augment.dart | 22 +++++++++++----------- lib/routingHandler.dart | 34 ++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 992f118..dc35805 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -237,13 +237,13 @@ class _DynamicClassesAugment extends State { // SizedBox(width: 8), ElevatedButton( onPressed: () => AugmentClasses() - .handleCreateLink(context, widget.emails, widget.subject), + .handleCreateLink(context, widget.emails, widget.subject, widget.emails[0]), //need to add the email ids child: Text('Create Link'), ), ElevatedButton( // onPressed: () => localAugment!.handlePaste(context), onPressed: () => - AugmentClasses().handlePaste(context, widget.emails[0]), + AugmentClasses().handlePaste(context), child: Text('Paste Link'), ), ], @@ -552,14 +552,14 @@ class AugmentClasses { } _copyLink(String anchor, String target, String format, String viewspecs, - String nameOfDocument) { - String form = "$anchor < $nameOfDocument, $target :$viewspecs >"; + String nameOfDocument, emailID) { + String form = "$anchor < $nameOfDocument, $target :$viewspecs > $emailID"; final link = ClipboardData(text: form); Clipboard.setData(link); } Future handleCreateLink(BuildContext context, - List emailsInThread, String nameOfDocument) async { + List emailsInThread, String nameOfDocument, String emailID) async { print("create link button pressed"); final TextEditingController targetController = TextEditingController(); final TextEditingController anchorController = TextEditingController(); @@ -670,7 +670,9 @@ class AugmentClasses { targetController.text, format, viewspecsController.text, - nameOfDocument), + nameOfDocument, + emailID, + ), Navigator.of(context).pop() }, child: Text("OK")), @@ -682,7 +684,7 @@ class AugmentClasses { )); } - Future handlePaste(BuildContext context, String emailID) async { + Future handlePaste(BuildContext context) async { final TextEditingController gotoLink = TextEditingController(); Routinghandler localRouting; @@ -716,18 +718,16 @@ class AugmentClasses { ElevatedButton( onPressed: () { print('pressed'); - // i need this shit to be processed into args - print("email_id given $emailID"); Navigator.of(context).pop(); final localRouting = - Routinghandler(gotoLink.text, emailID); + Routinghandler(gotoLink.text); final String subject = localRouting.docName; // This is your :subject final String target = localRouting.target; // This is your :target final String viewspecs = localRouting.viewspecs; // This is your :viewspecs - final String finalEmailID = emailID; + final String finalEmailID = localRouting.emailID; final encodedSubject = Uri.encodeComponent(subject); final encodedTarget = Uri.encodeComponent(target); diff --git a/lib/routingHandler.dart b/lib/routingHandler.dart index 55c7b26..1d51ba6 100644 --- a/lib/routingHandler.dart +++ b/lib/routingHandler.dart @@ -7,7 +7,7 @@ import 'api_service.dart'; import 'structs.dart'; class Routinghandler extends StatefulWidget { - Routinghandler(String link, emailID) { + Routinghandler(String link) { bool anchorDone = false; bool docNameDone = false; @@ -16,6 +16,8 @@ class Routinghandler extends StatefulWidget { bool targetDone = false; + bool emailIdDone = false; + for (int letter = 0; letter < link.length; letter++) { if (!anchorDone) { if (link[letter] != '<') { @@ -44,6 +46,8 @@ class Routinghandler extends StatefulWidget { } else { viewspecsDone = true; } + } else if (!emailIdDone) { + emailID += link[letter]; } } anchor = anchor.trim(); @@ -55,11 +59,11 @@ class Routinghandler extends StatefulWidget { } Routinghandler.fromParameters(String anchor, String docName, String target, String viewspecs, String emailID) { - this.anchor = anchor; - this.docName = docName; - this.viewspecs = viewspecs; - this.target = target; - this.emailID = emailID; + this.anchor = anchor; + this.docName = docName; + this.viewspecs = viewspecs; + this.target = target; + this.emailID = emailID; } Routinghandler.copyConstructor(Routinghandler other) { anchor = other.anchor; @@ -201,14 +205,16 @@ class _RoutingHandlerState extends State { ), ), Expanded( - child: Align( - alignment: Alignment.topLeft, - child: Wrap(children: [ - MarkdownBlock( - data: aug!.children![0]!.children![i]!.data ?? ''), - ],) - ) - ), + child: Align( + alignment: Alignment.topLeft, + child: Wrap( + children: [ + MarkdownBlock( + data: aug!.children![0]!.children![i]! + .data ?? + ''), + ], + ))), Padding( padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), child: Text( From 5ba6dcedf3fd754145ba1d10a51b852d4f5c6c94 Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 30 Aug 2025 01:10:11 -0400 Subject: [PATCH 62/64] small irrelevant updates --- lib/contact.dart | 2 -- lib/email.dart | 4 +++- lib/emailViewWeb.dart | 26 +++++++++++++------------- lib/home_page.dart | 26 ++++++++++++++++++-------- lib/main.dart | 16 ++-------------- lib/structs.dart | 2 +- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/lib/contact.dart b/lib/contact.dart index 32b4dae..3829055 100644 --- a/lib/contact.dart +++ b/lib/contact.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -// import 'package:http/http.dart' as http; -// import 'package:flutter_html/flutter_html.dart'; class ContactsPage extends StatefulWidget { const ContactsPage({super.key}); diff --git a/lib/email.dart b/lib/email.dart index 05a1378..bae89c4 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -78,6 +78,8 @@ class _EmailListScreenState extends State selectedEmails[email].seen = read; ApiService() .markAsSeen(selectedEmails[email].id); //the remote or .json + print(selectedEmails[email].id); + } } else { //unread @@ -85,7 +87,7 @@ class _EmailListScreenState extends State selectedEmails[email].seen = read; ApiService() .markAsUnseen(selectedEmails[email].id); //the remote or .json - print(selectedEmails[email].subject); + print(selectedEmails[email].id); } } }); diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart index a73c040..385af48 100644 --- a/lib/emailViewWeb.dart +++ b/lib/emailViewWeb.dart @@ -44,19 +44,18 @@ class _EmailViewState extends State { subject: widget.subject, rootAugment: localCollapsable.getAugmentRoot(), ); - - late CollapsableEmails localCollapsable = CollapsableEmails( - //change here - thread: widget.messages, //this wont work in serializable - // threadHTML: widget.emailContent, // old html - threadMarkdown: widget.emailContent, - threadIDs: widget.id, - targetJumpNumbering: _targetJumpNumbering, - targetViewspecs: _targetViewspecs, - targetFiltering: _queryFiltering, - nameOfDocument: widget.subject, - ); + late CollapsableEmails localCollapsable = CollapsableEmails( + //change here + thread: widget.messages, //this wont work in serializable + // threadHTML: widget.emailContent, // old html + threadMarkdown: widget.emailContent, + threadIDs: widget.id, + targetJumpNumbering: _targetJumpNumbering, + targetViewspecs: _targetViewspecs, + targetFiltering: _queryFiltering, + nameOfDocument: widget.subject, + ); final hardcodedMarkers = [ {'id': 'marker1', 'x': 50, 'y': 100}, @@ -75,6 +74,8 @@ class _EmailViewState extends State { .emailContent; //html of the email/ actually entire thread, gives me little space to play in between // i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse // _registerViewFactory(currentContent); + print("email content in Collapsable ${widget.emailContent}"); + } void _scrollToNumber(String spanId) { @@ -99,7 +100,6 @@ class _EmailViewState extends State { }); } - @override Widget build(BuildContext context) { ApiService.currThreadID = widget.id; diff --git a/lib/home_page.dart b/lib/home_page.dart index 5be194e..d4fe108 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -1,4 +1,5 @@ import 'package:crab_ui/sonicEmailView.dart'; +import 'package:go_router/go_router.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'folder_drawer.dart'; @@ -223,14 +224,22 @@ class _HomeScreenState extends State with TickerProviderStateMixin { leading: Icon(Icons.home), onTap: () { // Navigate to Home + context.go("/home"); }, ), - ListTile( - leading: Icon(Icons.settings), - onTap: () { - // Navigate to Settings - }, - ), + // ListTile( + // leading: Icon(Icons.settings), + // onTap: () { + // // Navigate to Settings + // }, + // ), + // ListTile( + // leading: Icon(Icons.contact_mail), + // onTap: () { + // // Navigate to Contacts + // }, + // ), + ListTile( leading: Icon(Icons.email), onTap: () { @@ -534,8 +543,9 @@ class _HomeScreenState extends State with TickerProviderStateMixin { "Selected folder: $selectedFolder"); // Store the selected folder or perform any action // ApiService.currFolder = selectedFolder!; - _emailPageKey.currentState! //the one selected - .moveSelectedOfFolder(selectedFolder!); + _emailPageKey + .currentState! //the one selected + .moveSelectedOfFolder(selectedFolder!); _overlayEntry ?.remove(); } else { diff --git a/lib/main.dart b/lib/main.dart index f5997de..935ce87 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,7 +22,6 @@ class HyM extends StatelessWidget { @override Widget build(BuildContext context) { final GoRouter _router = GoRouter( - // refreshListenable: , initialLocation: '/', routes: [ GoRoute( @@ -48,7 +47,8 @@ class HyM extends StatelessWidget { final target = state.pathParameters['target']!; final viewspecs = state.pathParameters['viewspecs']!; final emailId = state.pathParameters['emailID']!; - return Routinghandler.fromParameters("main anchor", subject, target, viewspecs, emailId); + return Routinghandler.fromParameters( + "main anchor", subject, target, viewspecs, emailId); }), ]); return MaterialApp.router( @@ -59,18 +59,6 @@ class HyM extends StatelessWidget { ), title: 'HyM', routerConfig: _router, - // home: HomeScreen(), - - // routes: { - // "/": (context) => SplashScreen(), - // "/login": (context) => const LoginPage(), - // "/home": (context) => HomeScreen(), - // "/contacts": (context) => ContactsPage(), - // GoRoute( - // path: - // ) - // "/email": (context) => EmailListScreen(), - // }, ); } } diff --git a/lib/structs.dart b/lib/structs.dart index 4b0a9ba..2499c57 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -81,7 +81,7 @@ class SerializableMessage { required this.subject, required this.date, required this.uid, - required this.list, //email list??? + required this.list, //folder required this.id, required this.in_reply_to, }); From d4819813955a26c515f5c6ae15fe768178b2862f Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 30 Aug 2025 01:10:27 -0400 Subject: [PATCH 63/64] get contacts endpoint --- lib/api_service.dart | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index 9c447a9..ecc433e 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -49,6 +49,7 @@ class ApiService { return []; } } + Future> fetchEmailsFromFolderReversed( String folder, int pagenitaion) async { try { @@ -57,7 +58,7 @@ class ApiService { 'limit': '50', 'offset': pagenitaion.toString(), }); - + var response = await http.get(url); List allEmails = []; @@ -82,6 +83,7 @@ class ApiService { return []; } } + Future fetchThreads( //populates allEmails, which is the List that contains all the emails in a thread int threadId, @@ -214,7 +216,6 @@ class ApiService { // SerializableMessage firstMail = mailsInSerializable[0]; - try { for (SerializableMessage mail in mailsInSerializable) { Map requestBody = { @@ -223,18 +224,19 @@ class ApiService { 'to': "Deleted Crabmail", }; var response = await http.post( - url, - headers: { - 'Content-Type': 'application/json', - }, - body: jsonEncode(requestBody), - ); - if (response.statusCode == 200) { - print('response body ${response.body}'); - return true; - } else { - print('error ${response.statusCode} ${response.body}'); - }} + url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode(requestBody), + ); + if (response.statusCode == 200) { + print('response body ${response.body}'); + return true; + } else { + print('error ${response.statusCode} ${response.body}'); + } + } } catch (e) { print("failed trying to post move_email, with error: $e"); } @@ -251,6 +253,17 @@ class ApiService { return []; } } + + Future> fetchContacts() async { + try { + var url = Uri.http('$ip:$port', 'get_contacts'); + var response = await http.get(url); + return List.from(json.decode(response.body)); + } catch (e) { + print('fetchFolders caught error: $e'); + return []; + } + } Future createFolder(String folderName) async { var url = Uri.http('$ip:$port', 'create_folder'); @@ -416,6 +429,7 @@ class ApiService { try { var url = Uri.http( '$ip:$port', 'post_seen_thread', {'id': thread_id.toString()}); + print("url: $url"); var response = await http.get(url); if (response.statusCode == 200) { var result = response.body; @@ -430,6 +444,7 @@ class ApiService { try { var url = Uri.http( '$ip:$port', 'post_unseen_thread', {'id': thread_id.toString()}); + print("url: $url"); var response = await http.get(url); if (response.statusCode == 200) { var result = response.body; From 0b382d72f47a7f6de81b5b3291d16ce21b639785 Mon Sep 17 00:00:00 2001 From: juan Date: Sun, 31 Aug 2025 23:00:53 -0400 Subject: [PATCH 64/64] nearly done login, only missing the other fields which are on the conf, but those should be made into a more user friendly way, where it should parse the email username, and from it fill out the imap server and what not --- android/app/build.gradle.kts | 8 ++ lib/login.dart | 211 ++++++++++++----------------------- pubspec.yaml | 1 + 3 files changed, 83 insertions(+), 137 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 86eb85d..fe27d63 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -28,6 +28,14 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + ndk { + abiFilters.addAll(listOf("arm64-v8a", "x86_64")) + } + sourceSets { + getByName("main") { + jniLibs.srcDirs("src/main/jniLibs") + } + } } buildTypes { diff --git a/lib/login.dart b/lib/login.dart index a03f4d1..1745355 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -12,21 +12,21 @@ import 'package:flutter/services.dart' show rootBundle; class AuthService extends ChangeNotifier { Future isUserLoggedIn() async { - ApiService.ip = '192.168.2.38'; - ApiService.port = '3001'; - print("setted up"); + // ApiService.ip = '127.0.0.1'; + // ApiService.port = '3001'; + // print("setted up"); - return true; + // return true; try { final response = - await http.get(Uri.http('localhost:6823', 'read-config')); - // await http.get(Uri.parse('http://localhost:6823/read-config')); + await http.get(Uri.http('127.0.0.1:6767', 'login_check')); print(response.statusCode); print(response.body); if (response.statusCode == 200) { final data = jsonDecode(response.body); - // return data['config']; + // print(data['state']); + // return true; try { var url = Uri.http('${data['ip']}:${data['port']}', 'is_logged_in'); var response = await http.get(url); @@ -106,18 +106,25 @@ class _SplashScreenState extends State { } Future _checkLoginStatus() async { - // SharedPreferences prefs = await SharedPreferences.getInstance(); - // print(prefs); - // bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false; - await Future.delayed(const Duration(seconds: 1)); - bool isLoggedIn = await _authService.isUserLoggedIn(); - print("is logged in $isLoggedIn"); - if (isLoggedIn) { - context.go("/home"); - // Navigator.pushReplacementNamed(context, '/home'); - } else { + try { + final response = + await http.get(Uri.parse('http://127.0.0.1:6767/login_check')); + print(response.statusCode); + print(response.body); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + print(data['state']); + if (data['state']) { + context.go("/home"); + } else { + context.go("/login"); + } + } else { + context.go("/login"); + } + } catch (e) { + print("caught in checkloginstatus in login $e"); context.go("/login"); - // Navigator.pushReplacementNamed(context, '/login'); } } @@ -139,14 +146,10 @@ class _LoginPageState extends State { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); - // final ConfigManager _configManager = - // ConfigManager("${Directory.current.parent}../crabmail2.conf"); - // Key to identify the form final _formKey = GlobalKey(); Future setIp(String ip) async { //this is not done :sob: :skull: - // _configManager.setField("api_addr", ip); return false; } @@ -155,31 +158,16 @@ class _LoginPageState extends State { } Future login() async { - bool result = await _handleLogin(); - if (result) { - Navigator.pushReplacementNamed(context, '/home'); + var result = await _handleLogin(); + if (result[0]) { + ApiService.ip = result[1]; + ApiService.port = result[2]; + context.go('/home'); } } - // Future _checkConfiguration() async { - // return false; - // } - - // void checkLogin() async { - // try { - // var url = Uri.http('127.0.0.1:3001', 'is_logged_in'); - // var response = await http.get(url); - // print(response.body); - // if (response.statusCode == 200) { - // print('all good on the west'); - // } - // } catch (e) { - // print(e); - // } - // // bool isLoggedIn = await _authService.isUserLoggedIn(); - // } // Function to handle login action - Future _handleLogin() async { + Future _handleLogin() async { if (_formKey.currentState!.validate()) { // Perform login action (e.g., authenticate with backend) String ip = _ipController.text; @@ -193,96 +181,64 @@ class _LoginPageState extends State { print(ip); print(port); - String baseUrl = "http://$ip:$port"; - print("baseurl " + baseUrl); - print(baseUrl); try { - final response = - await http.get(Uri.parse('http://localhost:6823/read-config')); + Map requestBody = { + "user": email, + "password": password, + "ip": ip, + "port": port, + }; + var url = Uri.http("$ip:6767", "login"); + + final response = await http.post(url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode( + requestBody)); //keep the port but change the ip to the server that will process it? print(response.statusCode); print(response.body); if (response.statusCode == 200) { final data = jsonDecode(response.body); - // return data['config']; + if (data["state"] == true) { + try { + final response = await http + .get(Uri.parse('http://127.0.0.1:6767/login_check')); + print(response.statusCode); + print(response.body); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + print(data['state']); + if (data['state']) { + context.go("/home"); + } else { + context.go("/login"); + } + } else { + context.go("/login"); + } + } catch (e) { + print("caught in checkloginstatus in login $e"); + context.go("/login"); + } + } + return [true, ip, port]; } + return [false]; } catch (e) { print("caught in catch"); print(e); - return false; + return [false]; } - Map updates = { - // "username": email, - // "password": password, - "ip": ip, - "port": port, - }; - print("past"); - - try { - final sending = await http.post( - Uri.parse('http://localhost:6823/update-config'), - headers: {'Content-Type': "application/json"}, - body: jsonEncode(updates)); - print("sending"); - } catch (e) { - print(e); - return false; - } - try { - // String status = await http.post(Uri.parse('')) - var url_log = Uri.http('$ip:$port', 'log_in'); - Map filteredData = { - "email": email, - "password": password, - // 'email': updates['username'], - // 'password': updates['password'] - }; - print(filteredData); - var status = await http.post( - url_log, - headers: { - 'Content-Type': 'application/json', - }, - body: jsonEncode(filteredData), - ); - if (status.statusCode == 200) { - print('response status ${status.body}'); - if (status.body == "Successful log in") { - ApiService.ip = ip; - ApiService.port = port; - return true; - } - } - } catch (e) { - print(e); - return false; - } - print("after"); - return false; - // final content = await _authService.readConfFile(); - // final config = await _authService.parseConfFile(content); - // print("BASE URL ${config["base_url"]}"); - // print("api address ${config["api_addr"]}"); - // print(config); - // const url = '' - // Clear the input fields - // _ipController.clear(); - // _portController.clear(); - // _emailController.clear(); - // _passwordController.clear(); } - return false; + return [false]; } @override Widget build(BuildContext context) { - // try { - // _configManager.loadConfig(); - // print(_configManager.getField('base_url')); - // } catch (e) { - // print("broke at build $e"); - // } - // _configManager. + _ipController.text = "127.0.0.1"; + _portController.text = "3001"; + return Center( child: Scaffold( appBar: AppBar( @@ -317,14 +273,6 @@ class _LoginPageState extends State { return 'Please enter your ip'; } }, - // onSaved: (value) async { - // final content = await _authService.readConfFile(); - // final config = - // await _authService.parseConfFile(content); - // print("BASE URL ${config["base_url"]}"); - // print("api address ${config["api_addr"]}"); - // //TODO: call a function to set the field ip in conf - // }, ), ), Container( @@ -403,17 +351,6 @@ class _LoginPageState extends State { child: const Text('Login'), ), ), - // SizedBox( - // width: 200, - // child: ElevatedButton( - // // onPressed: checkLogin, - // onPressed: () async { - // await _authService.isUserLoggedIn(); - // // print(result); - // }, - // child: const Text('checker'), - // ), - // ) ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index e1ef19b..284635b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: go_router: ^16.0.0 super_editor: ^0.3.0-dev.27 super_editor_markdown: 0.1.8 + dev_dependencies: flutter_test: