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 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ 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 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ 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 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ 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 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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 0000000..db77bb4 Binary files /dev/null and b/android_old/app/src/main/res/mipmap-hdpi/ic_launcher.png differ 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 0000000..17987b7 Binary files /dev/null and b/android_old/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android_old/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android_old/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android_old/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ 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 0000000..d5f1c8d Binary files /dev/null and b/android_old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ 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 0000000..4d6372e Binary files /dev/null and b/android_old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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/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..b5b127a --- /dev/null +++ b/lib/SonicEmailViewWeb.dart @@ -0,0 +1,150 @@ +import 'package:crab_ui/augment.dart'; +import 'package:web/web.dart' as web; +import 'dart:ui_web' as ui; +import 'dart:js_interop'; +import 'structs.dart'; +import 'package:flutter/material.dart'; + +class SonicEmailView extends StatefulWidget { + SerializableMessage email; + String emailHTML; + + SonicEmailView({required this.email, required this.emailHTML}); + + @override + _SonicEmailViewState createState() => _SonicEmailViewState(); +} + +class _SonicEmailViewState extends State { + String viewTypeIDs = ""; + int heightOFViewtype = 0; + bool _isLoaded = false; + + void _scrollToNumber(String spanId) { + AugmentClasses.handleJump(spanId); + } + void _handleViewspecs(String queryViewspecs) { + return; + } + + @override + void initState() { + super.initState(); + _init(); + } + + Future _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: () => {}, + onJumpToNumbering: _scrollToNumber, + onViewspecs: _handleViewspecs + ), + Row( + // title of email + children: [ + Text( + widget.email.subject, + style: TextStyle(fontSize: 30), + ), + ], + ), + Row( + children: [ + Text( + 'from ${widget.email.name}', + style: TextStyle(fontSize: 18), + ), + Text( + '<${widget.email.from}>', + style: TextStyle(fontSize: 18), + ), + Spacer(), + Text( + '${widget.email.date}', + textAlign: TextAlign.right, + ) + ], + ), + // TODO: make a case where if one of these is the user's email it just says me :))))) + Row( + children: [ + Text( + 'to ${widget.email.to.toString()}', + style: TextStyle(fontSize: 15), + ) + ], + ), + Expanded( + // child: SizedBox( + // height: heightOFViewtype.toDouble(), + child: HtmlElementView( + key: UniqueKey(), viewType: this.viewTypeIDs, + // ), + )) + ], + ), + ], + ), + ) + : const Center( + child: CircularProgressIndicator(), + ); + } +} diff --git a/lib/api_service.dart b/lib/api_service.dart index b0b92ea..ecd5a83 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -1,22 +1,12 @@ // this file should handle most of the API calls -// it also builds some widgets, but it will be modulated later +// it also builds some widgets, but it will be modulated later // chat it did import 'dart:async'; import 'dart:typed_data'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -import 'collapsableEmails.dart'; - import 'structs.dart'; -import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; -import 'dart:ui_web' as ui; -import 'augment.dart'; -// import 'dart:html' as html; -// import 'dart:js' as js; -import 'package:web/web.dart' as web; -import 'dart:js_interop' as js; class ApiService { static String ip = ""; @@ -148,7 +138,6 @@ class ApiService { } catch (e) { print('_getEmailContent caught error: $e'); } - // return content; return HTMLofThread; } @@ -351,308 +340,41 @@ class ApiService { return AttachmentResponse(name: "error", data: Uint8List(0)); } - //TODO: MOVE THIS INTO WEB -// Future>> 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 []; -// } -} - -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), - // ), - // ), - // ), - // ), - // ), - ], - )); + 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; } } 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) +// ); +// } +// } 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/lib/augment.dart b/lib/augment.dart index b957740..8b8fa60 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,23 +1,17 @@ -// import 'dart:ffi'; - import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/attachmentDownload.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; -import 'package:pdfrx/pdfrx.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -// import 'dart:html' as html; -// import 'dart:js' as js; -import 'package:web/web.dart' as web; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'attachmentWidget.dart'; class EmailToolbar extends StatefulWidget { - final Function(String) onJumpToSpan; + final Function(String) onJumpToNumbering; + final Function(String) onViewspecs; final VoidCallback onButtonPressed; EmailToolbar( - {Key? key, required this.onButtonPressed, required this.onJumpToSpan}) + {Key? key, required this.onButtonPressed, required this.onJumpToNumbering, required this.onViewspecs}) : super(key: key); @override @@ -26,7 +20,8 @@ class EmailToolbar extends StatefulWidget { class _DynamicClassesAugment extends State { String selectedClass = 'Class 1'; - // TextEditingController _jumpController = TextEditingController(); + TextEditingController _jumpController = TextEditingController(); + TextEditingController _viewspecsController = TextEditingController(); // late final FocusNode _JumpItemfocusNode; // late final FocusNode _viewSpecsfocusNode; @@ -53,7 +48,7 @@ class _DynamicClassesAugment extends State { void dispose() { // _JumpItemfocusNode.dispose(); // _viewSpecsfocusNode.dispose(); - // _jumpController.dispose(); + _jumpController.dispose(); super.dispose(); } @@ -78,20 +73,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); @@ -138,10 +133,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) @@ -149,7 +144,7 @@ class _DynamicClassesAugment extends State { onSubmitted: (value) { print("onSubmitted"); if (value.isNotEmpty) { - widget.onJumpToSpan(value); + widget.onJumpToNumbering(value); } }, ), @@ -186,14 +181,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( @@ -491,30 +490,8 @@ class AugmentClasses { print("Stop button pressed"); } - static void handleJump(String spanId) { - String js_code = ''' - var iframe = document.getElementsByTagName('iframe')[0]; // 0 for the first iframe, 1 for the second, etc. - - // Check if the iframe is loaded and has content - if (iframe && iframe.contentDocument) { - // Access the document inside the iframe - var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; - - // Find the element with the specific id inside the iframe - var targetElement = iframeDoc.getElementById("$spanId"); // Replace '36 ' with the actual id of the target element - - // If the element exists, scroll to it - if (targetElement) { - targetElement.scrollIntoView(); - console.log('Scrolled to element with id "$spanId" inside the iframe.'); - } else { - console.log('Element with id "$spanId" not found inside the iframe.'); - } - } else { - console.log('Iframe not found or not loaded.'); - } - '''; - // js.context.callMethod('eval', [js_code]); + static void handleJump(String value) { + print(value); } static void invisibility(String htmlClass) {} 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..d89afb4 --- /dev/null +++ b/lib/collapsableEmailsAndroid.dart @@ -0,0 +1,435 @@ +import 'package:flutter/material.dart'; +import 'api_service.dart'; +import 'structs.dart'; +import 'package:html2md/html2md.dart' as html2md; +import 'package:markdown_widget/markdown_widget.dart'; +import 'package:markdown/markdown.dart' as md; + +class CollapsableEmails extends StatefulWidget { + final List thread; // email id's in the form xyz@gmail.com + // final List threadHTML; + final List threadMarkdown; + final String threadIDs; + final String? targetJumpNumbering; + final String? targetViewspecs; + + CollapsableEmails( + {required this.thread, + required this.threadMarkdown, + required this.threadIDs, + this.targetJumpNumbering, + this.targetViewspecs, + }); + + @override + State 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 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 = []; + List allMarkdown = []; + List> sentinel = []; + int level = 0; + AugmentTree zoomTreeRoot = AugmentTree(); + // 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(); + threadNodes = []; + currentZoomTree = []; + // _markdownConverter(); + _serializableData(widget.threadIDs); // this + _markdown2Tree(widget.threadMarkdown); + } + @override + void didUpdateWidget(covariant CollapsableEmails oldWidget) { + // TODO: implement didUpdateWidget + super.didUpdateWidget(oldWidget); + if (widget.targetJumpNumbering != null && + widget.targetJumpNumbering != oldWidget.targetJumpNumbering) { + _handleJump(widget.targetJumpNumbering!); + } + if (widget.targetViewspecs != null && + widget.targetViewspecs != oldWidget.targetViewspecs) { + _handleViewspecs(widget.targetViewspecs!); + } + } + + @override + void dispose() { + super.dispose(); + } + + void _add2Tree(AugmentTree tree, md.Element node2add) { + // adds node to its corresponding place + AugmentTree newNode = AugmentTree(); + newNode.setData(node2add.textContent); + newNode.ogTag = node2add.tag; + // cases, + //1. a node that comes is lower than the root.children last, if so it goes beneath it + if (tree.children.isEmpty) { + // new level to be created when totally empty + tree.children.add(newNode); + newNode.parent = tree; + } else if (tree.children.isNotEmpty && + tree.children.last.ogTag.isNotEmpty) { + if ((hirarchyDict[node2add.tag] ?? + -1) < // e.g. new node is h1 and old is h2, heapify + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + //have to figure out the borthers + //assuming it all goes right + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + return; + } else if (tree.children.last.parent == null) { + // becomes the new top level + for (AugmentTree brother in tree.children) { + brother.parent = newNode; + } + tree.children = [newNode]; + } else { + newNode.parent = tree; + tree.children.add(newNode); + } + } else if ((hirarchyDict[node2add.tag] ?? + -1) > // go down e.g. new node is h3 and old is h2 or something + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + print("-1 ${tree.children.last.ogTag}"); + return; + } + + _add2Tree(tree.children.last, node2add); + } else if ((hirarchyDict[node2add.tag] ?? -1) == + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + tree.children.add(newNode); + newNode.parent = tree; + } + } + } + + void _markdown2Tree(List text) { + print("started markdown2tree"); + 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); + } + + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + void _goToChildren(int indexThread, int index) async { + final target = currentZoomTree[indexThread].children[index]; + if (target.children.isNotEmpty) { + setState(() { + currentZoomTree[indexThread] = target; + }); + } else { + print("This child has no further children."); + } + } + + void _goToParent(int indexThread) async { + if (currentZoomTree[indexThread].parent != null) { + setState(() { + currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!; + }); + } else { + print("Already at root."); + } + } + + void _serializableData(String threadID) async { + emailsInThread = await ApiService().threadsInSerializable(threadID); + print("done thread serializable"); + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + Widget _buildForZooms(int indexThread) { + // IF I GIVE IT THE INDEX???? + if (!_isLoaded) { + return const Center(child: CircularProgressIndicator()); // loading screen + } + + final AugmentTree currentZoomNodeForThisEmail = + currentZoomTree[indexThread]; + + final canZoomOut = currentZoomNodeForThisEmail.parent != null; + + return ListView.builder( + itemCount: currentZoomNodeForThisEmail.children.length, + itemBuilder: (context, index) { + final childNode = currentZoomNodeForThisEmail.children[index]; + final canZoomIn = childNode.children.isNotEmpty; + // currentZoomNodeForThisEmail.addNumbering(); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), + child: Material( + elevation: 1, + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 4.0, + children: [ + OutlinedButton( + onPressed: + canZoomOut ? () => _goToParent(indexThread) : null, + child: Icon(Icons.north_west_sharp), + ), + OutlinedButton( + onPressed: canZoomIn + ? () => _goToChildren(indexThread, index) + : null, + child: Icon(Icons.south_east_sharp), + ), + ], + ), + SizedBox(width: 12.0), + if (leftNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + Expanded( + child: MarkdownBlock( + data: childNode.data, + // data: currentZoomNode + // .children[index].data, // one string of markdown + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + if (rightNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + void _handleJump(String queryNumbering) { + print(queryNumbering); + if (queryNumbering.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please enter a numbering to jump to.')), + ); + return; + } + + final int targetEmailIndex = _expandedEmails.first; + if (targetEmailIndex >= threadNodes.length) { + // Error handling + return; + } + + final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex]; + final AugmentTree? foundNode = + _findNodeByNumbering(rootOfCurrentEmail, queryNumbering); + + if (foundNode != null) { + setState(() { + currentZoomTree[targetEmailIndex] = foundNode; // Update the state + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Numbering "$queryNumbering" not found.')), + ); + } + } + + void _handleViewspecs(String viewspecsQuery) { + print(viewspecsQuery); + if (viewspecsQuery.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please enter the viewspecs.')), + ); + return; + } + + final int targetEmailIndex = _expandedEmails.first; + if (targetEmailIndex >= threadNodes.length) { + // Error handling + return; + } + + if (viewspecsQuery.contains('n')) { + setState(() { + leftNumbering = false; // Update the state + rightNumbering = false; + }); + } + if (viewspecsQuery.contains('m')) { + setState(() { + rightNumbering = true; + leftNumbering = true; + }); + } + if (viewspecsQuery.contains('H')) { + setState(() { + leftNumbering = !leftNumbering; + }); + } + if (viewspecsQuery.contains('G')) { + setState(() { + rightNumbering = !rightNumbering; + }); + } + if (viewspecsQuery.contains('w')) { + setState(() { + showWhole = true; + }); + } + + // else { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')), + // ); + // } + } + + AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) { + //recursively finds the node you mentioned + // to find the AugmentTree node corresponding to the `numbering`. + if (root.numbering == numbering) { + return root; + } + for (var child in root.children) { + final found = _findNodeByNumbering(child, numbering); + if (found != null) { + return found; + } + } + return null; + } + + @override + Widget build(BuildContext context) { + return _isLoaded + ? Column(children: [ + Expanded( + child: ListView.builder( + itemCount: emailsInThread.length, + itemBuilder: (context, index) { + final isExpanded = _expandedEmails + .contains(index); //check if email is expanded + return Column( + children: [ + ListTile( + title: Text(emailsInThread[index].from), + trailing: Text(emailsInThread[index].date), + onTap: () { + setState(() { + if (isExpanded) { + _expandedEmails.remove(index); + } else { + _expandedEmails.add(index); + } + }); + }, + ), + if (isExpanded) + ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100, + maxHeight: + MediaQuery.of(context).size.height * 0.6, + ), + child: _buildForZooms(index), + ), + Divider(), + ], + ); + }, + ), + ), + ]) + : const Center(child: CircularProgressIndicator()); + } +} diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart new file mode 100644 index 0000000..dd9168f --- /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 threadMarkdown; + final String threadIDs; + + CollapsableEmails( + {required this.thread, + required this.threadMarkdown, + required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs}); + + @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..1183259 --- /dev/null +++ b/lib/collapsableEmailsWeb.dart @@ -0,0 +1,450 @@ +import 'package:english_words/english_words.dart'; +import 'package:flutter/material.dart'; +import 'api_service.dart'; +import 'structs.dart'; +import 'package:html2md/html2md.dart' as html2md; +import 'package:markdown_widget/markdown_widget.dart'; +import 'package:markdown/markdown.dart' as md; + +class CollapsableEmails extends StatefulWidget { + final List thread; // email id's in the form xyz@gmail.com + // 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.threadMarkdown, + required this.threadIDs, + this.targetJumpNumbering, + this.targetViewspecs, + }); + + @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 emailsInThread = []; + bool _isLoaded = false; + List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; + Map hirarchyDict = { + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4, + "h5": 5, + "h6": 6, + "p": 8, + "ul": 8, + "li": 8, + }; + + List tagsCollected = []; + List allMarkdown = []; + List> sentinel = []; + int level = 0; + AugmentTree zoomTreeRoot = AugmentTree(); + // late AugmentTree currentZoomNode; + late List currentZoomTree = []; + bool zoomOut = false; + bool zoomIn = true; + late List threadNodes = []; + static bool leftNumbering = true; + static bool rightNumbering = true; + bool showWhole = false; + + @override + void initState() { + super.initState(); + threadNodes = []; + currentZoomTree = []; + // _markdownConverter(); + _serializableData(widget.threadIDs); // this + _markdown2Tree(widget.threadMarkdown); + } + + @override + void didUpdateWidget(covariant CollapsableEmails oldWidget) { + // TODO: implement didUpdateWidget + super.didUpdateWidget(oldWidget); + if (widget.targetJumpNumbering != null && + widget.targetJumpNumbering != oldWidget.targetJumpNumbering) { + _handleJump(widget.targetJumpNumbering!); + } + if (widget.targetViewspecs != null && + widget.targetViewspecs != oldWidget.targetViewspecs) { + _handleViewspecs(widget.targetViewspecs!); + } + } + + @override + void dispose() { + super.dispose(); + } + + // void _markdownConverter() async { + // // to list of markdown + // // for (int email = 0; email < widget.threadHTML.length; email++) { + // // String markdown = html2md.convert(widget.threadHTML[email]); + // // allMarkdown.add(markdown); + // // } + // for (int email = 0; email < widget.threadMarkdown.length; email++) { + // allMarkdown.add(email); + // } + // } + + void _add2Tree(AugmentTree tree, md.Element node2add) { + // adds node to its corresponding place + AugmentTree newNode = AugmentTree(); + newNode.setData(node2add.textContent); + newNode.ogTag = node2add.tag; + // cases, + //1. a node that comes is lower than the root.children last, if so it goes beneath it + if (tree.children.isEmpty) { + // new level to be created when totally empty + tree.children.add(newNode); + newNode.parent = tree; + } else if (tree.children.isNotEmpty && + tree.children.last.ogTag.isNotEmpty) { + if ((hirarchyDict[node2add.tag] ?? + -1) < // e.g. new node is h1 and old is h2, heapify + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + //have to figure out the borthers + //assuming it all goes right + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + return; + } else if (tree.children.last.parent == null) { + // becomes the new top level + for (AugmentTree brother in tree.children) { + brother.parent = newNode; + } + tree.children = [newNode]; + } else { + newNode.parent = tree; + tree.children.add(newNode); + } + } else if ((hirarchyDict[node2add.tag] ?? + -1) > // go down e.g. new node is h3 and old is h2 or something + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + if ((hirarchyDict[node2add.tag] ?? -1) == -1 || + (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { + print( + 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); + print("-1 ${tree.children.last.ogTag}"); + return; + } + + _add2Tree(tree.children.last, node2add); + } else if ((hirarchyDict[node2add.tag] ?? -1) == + (hirarchyDict[tree.children.last.ogTag] ?? -1)) { + tree.children.add(newNode); + newNode.parent = tree; + } + } + } + + void _markdown2Tree(List text) { + print("started markdown2tree"); + 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); + } + + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + void _goToChildren(int indexThread, int index) async { + final target = currentZoomTree[indexThread].children[index]; + if (target.children.isNotEmpty) { + setState(() { + currentZoomTree[indexThread] = target; + }); + } else { + print("This child has no further children."); + } + } + + void _goToParent(int indexThread) async { + if (currentZoomTree[indexThread].parent != null) { + setState(() { + currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!; + }); + } else { + print("Already at root."); + } + } + + void _serializableData(String threadID) async { + emailsInThread = await ApiService().threadsInSerializable(threadID); + print("done thread serializable"); + + if (!mounted) return; + setState(() { + _isLoaded = true; + }); + } + + Widget _buildForZooms(int indexThread) { + // IF I GIVE IT THE INDEX???? + if (!_isLoaded) { + return const Center(child: CircularProgressIndicator()); // loading screen + } + + final AugmentTree currentZoomNodeForThisEmail = + currentZoomTree[indexThread]; + + final canZoomOut = currentZoomNodeForThisEmail.parent != null; + + return ListView.builder( + itemCount: currentZoomNodeForThisEmail.children.length, + itemBuilder: (context, index) { + final childNode = currentZoomNodeForThisEmail.children[index]; + final canZoomIn = childNode.children.isNotEmpty; + // currentZoomNodeForThisEmail.addNumbering(); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), + child: Material( + elevation: 1, + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surface, + surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 4.0, + children: [ + OutlinedButton( + onPressed: + canZoomOut ? () => _goToParent(indexThread) : null, + child: Icon(Icons.north_west_sharp), + ), + OutlinedButton( + onPressed: canZoomIn + ? () => _goToChildren(indexThread, index) + : null, + child: Icon(Icons.south_east_sharp), + ), + ], + ), + SizedBox(width: 12.0), + if (leftNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + Expanded( + child: MarkdownBlock( + data: childNode.data, + // data: currentZoomNode + // .children[index].data, // one string of markdown + config: MarkdownConfig + .darkConfig, // or lightConfig depending on theme + ), + ), + if (rightNumbering) + Padding( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 0), + child: Text( + childNode.numbering, + style: + TextStyle(color: Color(Colors.purple[400]!.value)), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + void _handleJump(String queryNumbering) { + print(queryNumbering); + if (queryNumbering.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please enter a numbering to jump to.')), + ); + return; + } + + final int targetEmailIndex = _expandedEmails.first; + if (targetEmailIndex >= threadNodes.length) { + // Error handling + return; + } + + final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex]; + final AugmentTree? foundNode = + _findNodeByNumbering(rootOfCurrentEmail, queryNumbering); + + if (foundNode != null) { + setState(() { + currentZoomTree[targetEmailIndex] = foundNode; // Update the state + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Numbering "$queryNumbering" not found.')), + ); + } + } + + void _handleViewspecs(String viewspecsQuery) { + print(viewspecsQuery); + if (viewspecsQuery.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please enter the viewspecs.')), + ); + return; + } + + final int targetEmailIndex = _expandedEmails.first; + if (targetEmailIndex >= threadNodes.length) { + // Error handling + return; + } + + if (viewspecsQuery.contains('n')) { + setState(() { + leftNumbering = false; // Update the state + rightNumbering = false; + }); + } + if (viewspecsQuery.contains('m')) { + setState(() { + rightNumbering = true; + leftNumbering = true; + }); + } + if (viewspecsQuery.contains('H')) { + setState(() { + leftNumbering = !leftNumbering; + }); + } + if (viewspecsQuery.contains('G')) { + setState(() { + rightNumbering = !rightNumbering; + }); + } + if (viewspecsQuery.contains('w')) { + setState(() { + showWhole = true; + }); + } + + // else { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')), + // ); + // } + } + + AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) { + //recursively finds the node you mentioned + // to find the AugmentTree node corresponding to the `numbering`. + if (root.numbering == numbering) { + return root; + } + for (var child in root.children) { + final found = _findNodeByNumbering(child, numbering); + if (found != null) { + return found; + } + } + return null; + } + + @override + Widget build(BuildContext context) { + return _isLoaded + ? Column(children: [ + Expanded( + child: ListView.builder( + itemCount: emailsInThread.length, + itemBuilder: (context, index) { + final isExpanded = _expandedEmails + .contains(index); //check if email is expanded + return Column( + children: [ + ListTile( + title: Text(emailsInThread[index].from), + trailing: Text(emailsInThread[index].date), + onTap: () { + setState(() { + if (isExpanded) { + _expandedEmails.remove(index); + } else { + _expandedEmails.add(index); + } + }); + }, + ), + if (isExpanded) + ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100, + maxHeight: MediaQuery.of(context).size.height * 0.6, + ), + child: _buildForZooms(index), //show the tree + // child: _buildForZooms(key: ValueKey(currentZoomNode)), + ), + Divider(), + ], + ); + }, + ), + ), + ]) + : const Center(child: CircularProgressIndicator()); + } +} 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..0b1ded3 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; @@ -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/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..7b257a2 --- /dev/null +++ b/lib/emailViewAndroid.dart @@ -0,0 +1,115 @@ +import 'package:crab_ui/augment.dart'; +import 'package:crab_ui/collapsableEmailsAndroid.dart'; +import 'package:flutter/material.dart'; +// import 'dart:ui_web' as ui; +// import 'augment.dart'; +// // import 'dart:js_interop' as js; //eventually for manipulating css +// import 'package:pointer_interceptor/pointer_interceptor.dart'; +// import 'collapsableEmails.dart'; +// import 'api_service.dart'; + +class EmailView extends StatefulWidget { + final List 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 + void initState() { + super.initState(); + } + + void _scrollToNumber(String spanId) { + // AugmentClasses.handleJump(spanId); + } + void _viewSpecs(String command){ + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.name), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + EmailToolbar( + onButtonPressed: () => {}, + onJumpToNumbering: _scrollToNumber, + onViewspecs: _viewSpecs, + + ), + Row( + children: [ + Expanded( + child: Text( + widget.subject, + style: TextStyle(fontSize: 15), + overflow: TextOverflow.visible, + softWrap: true, + ), + ), + ], + ), + Row( + children: [ + Text( + 'from ${widget.name}', + style: TextStyle(fontSize: 8), + ), + Text( + '<${widget.from}>', + style: TextStyle(fontSize: 8), + ), + Spacer(), + Text( + widget.date, + textAlign: TextAlign.right, + ) + ], + ), + Row( + children: [ + Text( + 'to ${widget.to.toString()}', + style: TextStyle(fontSize: 8), + ) + ], + ), + Expanded( + child: CollapsableEmails( + thread: widget.messages, + threadMarkdown: widget.emailContent, + threadIDs: widget.id, + ), + ), + ], + ), + ) + ); + } +} \ 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..ceb4ef1 --- /dev/null +++ b/lib/emailViewWeb.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'dart:ui_web' as ui; +import 'augment.dart'; +// import 'dart:js_interop' as js; //eventually for manipulating css +import 'collapsableEmails.dart'; +import 'api_service.dart'; + +class EmailView extends StatefulWidget { + final List 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}, + ]; + String? _targetJumpNumbering; + String? _targetViewspecs; + + @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 _scrollToNumber(String spanId) { + AugmentClasses.handleJump(spanId); + } + + void _handleJumpRequest(String numbering) { + setState(() { + _targetJumpNumbering = numbering; + }); + } + + void _handleViewspecsRequest(String viewspecsCommand) { + setState(() { + _targetViewspecs = viewspecsCommand; + }); + } + + // TODO: void _invisibility(String ) //to make purple numbers not visible + + @override + Widget build(BuildContext context) { + ApiService.currThreadID = widget.id; + return Scaffold( + appBar: AppBar( + title: Text(widget.name), + ), + body: Stack( + children: [ + Column( + children: [ + EmailToolbar( + onJumpToNumbering: _handleJumpRequest, + onViewspecs: _handleViewspecsRequest, + onButtonPressed: () => {print("email tool bar pressed")}, + ), + Row( + // title of email + children: [ + Text( + widget.subject, + style: TextStyle(fontSize: 30), + ), + ], + ), + Row( + children: [ + Text( + 'from ${widget.name}', + style: TextStyle(fontSize: 18), + ), + Text( + '<${widget.from}>', + style: TextStyle(fontSize: 18), + ), + Spacer(), + Text( + '${widget.date}', + textAlign: TextAlign.right, + ) + ], + ), + // TODO: make a case where if one of these is the user's email it just says me :))))) + Row( + children: [ + Text( + 'to ${widget.to.toString()}', + style: TextStyle(fontSize: 15), + ) + ], + ), + Expanded( + child: CollapsableEmails( + //change here + thread: widget.messages, //this wont work in serializable + // threadHTML: widget.emailContent, // old html + threadMarkdown: widget.emailContent, + threadIDs: widget.id, + targetJumpNumbering: _targetJumpNumbering, + targetViewspecs: _targetViewspecs, + ), + ), + ], + ), + ], + )); + } +} diff --git a/lib/home_page.dart b/lib/home_page.dart index b4878b0..2849444 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'; @@ -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: - // // ), - // ], + ); - // ); } }, ); @@ -201,265 +171,253 @@ 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( - children: [ - // Sidebar - if (_isSidebarOpen) - Container( - width: 70, - color: Color.fromARGB(17, 96, 122, 135), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + 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: [ + Row( 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; - }); - }, + // 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; + }); + }, + ), + ), + ), + ], ), ), + // 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; + // }); + }, + ), + ), + ), + ), + SizedBox( + width: 8, + ), + 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), + ), + 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), + ), + ], + ), + ), + 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(), + // ), + // ), + ], + ), ), ], ), - ), - // 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: [ - Container( - width: 800, - 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; - // }); - }, - ), - ), - SizedBox( - width: 16, - ), - Container( - width: 80, - height: 40, - child: ElevatedButton( - onPressed: _showOptionsSearchDialog, - child: Icon(Icons.manage_search), - ), - ) - ], + if (!_isSidebarOpen) + Positioned( + bottom: 16, + left: 16, + child: FloatingActionButton( + child: Icon(Icons.menu), + onPressed: () { + setState(() { + _isSidebarOpen = true; + }); + }, ), ), - 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), - ), - 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), - ), - ], - ), - ), - 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; - }); - }, + ], ), ), - ], + ), + ), ), ); } } -// 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'), -// ), -// ], -// ); -// } -// } 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: "/", 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 diff --git a/lib/structs.dart b/lib/structs.dart index c7e9da4..e99c0d2 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,67 @@ 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 = []; + + String data = ''; + AugmentTree? parent; + String ogTag = ''; + String numbering = ''; + + void setData(String data) { + this.data = data; + } + + static String _intToLetter(int index) { + return String.fromCharCode('a'.runes.first + index); + } + + void addNumbering({String prefix = ''}) { + //if called in root, numbers them all + for (int i = 0; i < children.length; i++) { + final child = children[i]; + String childNumbering; + bool parentIsLettered = prefix.contains(RegExp(r'[a-z]')); + if (prefix.isEmpty) { + parentIsLettered = false; + } else { + parentIsLettered = prefix.runes.last >= 'a'.runes.first && + prefix.runes.last <= 'z'.runes.first; + } + + if (prefix.isEmpty) { + // Top-level children (direct children of the original root being numbered) get 1, 2, 3... + childNumbering = (i + 1).toString(); + } else if (parentIsLettered) { + // Deeper children get '1a', '1b', '2a', '2b', etc. + childNumbering = '$prefix${i + 1}'; + } else { + childNumbering = '$prefix${_intToLetter(i)}'; + } + + child.numbering = childNumbering; + + // Recursively call for children + child.addNumbering(prefix: childNumbering); + } + } +} + + +class MarkdownParsed{ + final String text; + MarkdownParsed({required this.text}); + factory MarkdownParsed.fromJson(Map json){ + return MarkdownParsed( + text: json['md'] ?? '', + ); + } +} + diff --git a/pubspec.lock b/pubspec.lock index f4b7708..13a53ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,106 +5,98 @@ 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" + version: "2.13.0" audio_session: dependency: transitive description: name: audio_session - sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" + sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" url: "https://pub.dev" source: hosted - version: "0.1.21" + version: "0.1.25" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" cached_network_image: dependency: transitive description: name: cached_network_image - sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7 + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.1.1" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" chewie: dependency: transitive description: name: chewie - sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1" + sha256: "4d9554a8f87cc2dc6575dfd5ad20a4375015a29edd567fd6733febe6365e2566" 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.11.3" 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: @@ -125,10 +117,10 @@ packages: dependency: transitive description: name: csslib - sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted - version: "0.17.3" + version: "1.0.2" cupertino_icons: dependency: transitive description: @@ -137,22 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dio: + dbus: dependency: transitive description: - name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" 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" + version: "0.7.11" encrypt: dependency: "direct main" description: @@ -173,42 +157,34 @@ 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" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -218,74 +194,10 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" 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" + version: "3.4.1" flutter_layout_grid: dependency: "direct overridden" description: @@ -314,10 +226,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 @@ -332,74 +244,74 @@ packages: dependency: "direct main" description: name: flutter_widget_from_html - sha256: "8d2a9a7979a9c1a5d866d1f4134d2ec2cca78716c112c76803d6a552281405cc" + sha256: "0dfebf7417df2149de93926520c703db9be0c9017e60dc5cf43cebed37f4d11e" url: "https://pub.dev" source: hosted - version: "0.10.6" + version: "0.16.0" flutter_widget_from_html_core: dependency: transitive description: name: flutter_widget_from_html_core - sha256: "22140caa191cb4bba0fe4d5e4ad875c7e8a9ba47d61517f56d733019cf76396d" + sha256: f77ea1aa1ba29a38fcce04483f44f12382f541b9e8c2150df37166c23bbbd30f url: "https://pub.dev" source: hosted - version: "0.10.6" + version: "0.16.0" fwfh_cached_network_image: dependency: transitive description: name: fwfh_cached_network_image - sha256: "3de22aa3a6943c968e0d9fbcba4463b3dbbf7103171d62c84b6c672fb83eebdf" + sha256: "8f4896109ff3e42424ccacf9058ba3afe5d575b58946c8ac646ac85ae882ce23" url: "https://pub.dev" source: hosted - version: "0.7.0+7" + version: "0.16.0" fwfh_chewie: dependency: transitive description: name: fwfh_chewie - sha256: "0b51a1c976bb74da5e8e45d545c74cb54a7168ad3938dd77103a7aee485f55fa" + sha256: "1ce7c56894db19881a997813b933835dec142878431370c0eb40f1f878396a25" url: "https://pub.dev" source: hosted - version: "0.7.1+4" + version: "0.16.0" fwfh_just_audio: dependency: transitive description: name: fwfh_just_audio - sha256: "237b93a4cb9f0495a4b51940f361adda2a5abd57231dd44f07459db00144a6cd" + sha256: "17816168de1fd180fd3d1fd4500e23136630a248a6889b553e2d2067e133c1a6" url: "https://pub.dev" source: hosted - version: "0.9.0+3" + version: "0.16.0" fwfh_svg: dependency: transitive description: name: fwfh_svg - sha256: c6bb6b513f7ce2766aba76d7276caf9a96b6fee729ac3a492c366a42f82ef02e + sha256: "82f3eb378186fe39b3e2e01ed48a1830d34b0b9a237d951077e74ff0d99e2ac3" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.16.0" fwfh_url_launcher: dependency: transitive description: name: fwfh_url_launcher - sha256: b9f5d55a5ae2c2c07243ba33f7ba49ac9544bdb2f4c16d8139df9ccbebe3449c + sha256: "5cf1b1baa16740abaef8eb41a8e16ba430295d5ec20b880e4cb94e2924774f0a" url: "https://pub.dev" source: hosted - version: "0.9.1" + version: "0.16.0" fwfh_webview: dependency: transitive description: name: fwfh_webview - sha256: "90a8dda0695403cf57abd7e8b83f6fb1f1a12933930a0bf9cac7cafb06e06a18" + sha256: "894aa7d98ebdc2d86d79ac2309173043dec7f102575de87bf9626ddb26104e49" url: "https://pub.dev" source: hosted - version: "0.9.0+2" + version: "0.15.4" html: dependency: transitive description: name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "0.15.6" http: dependency: "direct main" description: @@ -412,10 +324,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 +340,50 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" just_audio: dependency: transitive description: name: just_audio - sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b + sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e url: "https://pub.dev" source: hosted - version: "0.9.37" + version: "0.9.46" just_audio_platform_interface: dependency: transitive description: name: just_audio_platform_interface - sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" + sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.0" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70" + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.4.16" 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 +400,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - list_counter: + logging: dependency: transitive description: - name: list_counter - sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.3.0" 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: @@ -544,22 +456,38 @@ packages: 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: 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 +500,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 +540,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 +564,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 +620,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 +684,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,15 +700,15 @@ 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" + version: "1.10.1" sprintf: dependency: transitive description: @@ -793,66 +721,90 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "2.3.3+1" + 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: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.4" + 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: 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 +817,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 +833,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 +873,50 @@ 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" + version: "3.1.4" uuid: dependency: transitive description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" 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: @@ -977,90 +929,66 @@ packages: dependency: transitive description: name: video_player - sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + sha256: "7d78f0cfaddc8c19d4cb2d3bebe1bfef11f2103b0a03e5398b303a1bf65eeb14" url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.5" video_player_android: dependency: transitive description: name: video_player_android - sha256: b6f0a6d241e4a3435806cb7cb78cb666db8889c1866e432b6acd204707b3ac01 + sha256: "28dcc4122079f40f93a0965b3679aff1a5f4251cf79611bd8011f937eb6b69de" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.8.4" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c + sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.7.1" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" + sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.5" vm_service: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" - wakelock: + version: "15.0.0" + wakelock_plus: dependency: transitive description: - name: wakelock - sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db" + name: wakelock_plus + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 url: "https://pub.dev" source: hosted - version: "0.6.2" - wakelock_macos: + version: "1.3.2" + wakelock_plus_platform_interface: dependency: transitive description: - name: wakelock_macos - sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd" + name: wakelock_plus_platform_interface + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 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: "1.2.3" web: dependency: "direct main" description: @@ -1073,50 +1001,50 @@ packages: dependency: transitive description: name: webview_flutter - sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522" + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.13.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9 + sha256: f6e6afef6e234801da77170f7a1847ded8450778caf2fe13979d140484be3678 url: "https://pub.dev" source: hosted - version: "3.16.6" + version: "4.7.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + sha256: "7cb32b21825bd65569665c32bb00a34ded5779786d6201f5350979d2d529940d" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.13.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "9c62cc46fa4f2d41e10ab81014c1de470a6c6f26051a2de32111b2ee55287feb" + sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3 url: "https://pub.dev" source: hosted - version: "3.14.0" + version: "3.22.0" win32: dependency: transitive description: name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "5.13.0" 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 +1054,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..c425b10 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 @@ -28,6 +26,10 @@ dependencies: pdfrx: ^1.0.94 photo_view: ^0.15.0 web: ^1.1.1 + flutter_widget_from_html: ^0.16.0 + html2md: ^1.3.2 + markdown_widget: ^2.3.2+8 + markdown: ^7.3.0 dev_dependencies: flutter_test: @@ -39,6 +41,7 @@ dependency_overrides: flutter_layout_grid: 2.0.7 flutter_math_fork: 0.7.2 + flutter: uses-material-design: true assets: