Compare commits

...

11 Commits

67 changed files with 1848 additions and 994 deletions

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2"
channel: "stable"
project_type: app
@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: android
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: ios
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: linux
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: macos
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: web
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: windows
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
# User provided section

14
android/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.crab_ui"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.crab_ui"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="crab_ui"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package com.example.crab_ui
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

21
android/build.gradle.kts Normal file
View File

@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

13
android_old/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
android {
namespace = "com.example.hym_ui"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.hym_ui"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="hym_ui"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package com.example.hym_ui
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

32
android_old/build.gradle Normal file
View File

@ -0,0 +1,32 @@
buildscript {
ext.kotlin_version = '1.9.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip

View File

@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

View File

@ -0,0 +1,19 @@
import 'structs.dart';
import 'package:flutter/material.dart';
class SonicEmailView extends StatefulWidget {
SerializableMessage email;
String emailHTML;
SonicEmailView({required this.email, required this.emailHTML});
@override
_SonicEmailViewState createState() => _SonicEmailViewState();
}
class _SonicEmailViewState extends State<SonicEmailView> {
@override
Widget build(BuildContext context) {
return Scaffold(body: Text("sonic email android"));
}
}

View File

@ -0,0 +1,22 @@
import 'structs.dart';
import 'package:flutter/material.dart';
class SonicEmailView extends StatefulWidget {
SerializableMessage email;
String emailHTML;
SonicEmailView({required this.email, required this.emailHTML});
@override
_SonicEmailViewState createState() => _SonicEmailViewState();
}
class _SonicEmailViewState extends State<SonicEmailView> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Text("sonic email stub")
);
}
}

145
lib/SonicEmailViewWeb.dart Normal file
View File

@ -0,0 +1,145 @@
import 'package:crab_ui/augment.dart';
import 'package:web/web.dart' as web;
import 'dart:ui_web' as ui;
import 'dart:js_interop';
import 'structs.dart';
import 'package:flutter/material.dart';
class SonicEmailView extends StatefulWidget {
SerializableMessage email;
String emailHTML;
SonicEmailView({required this.email, required this.emailHTML});
@override
_SonicEmailViewState createState() => _SonicEmailViewState();
}
class _SonicEmailViewState extends State<SonicEmailView> {
String viewTypeIDs = "";
int heightOFViewtype = 0;
bool _isLoaded = false;
void _scrollToNumber(String spanId) {
AugmentClasses.handleJump(spanId);
}
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
await _registerViewFactory(widget.emailHTML);
if (!mounted) return;
setState(() {
_isLoaded = true;
});
}
Future<void> _registerViewFactory(String currentContent) async {
// setState(() { //update to do item per item
// each item to have itsviewtype ID
// is this necessarey here??
//could just move to collapsable
// for (var emailHTML in widget.threadHTML) {
String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
final ghost = web.document.createElement('div') as web.HTMLDivElement
..style.visibility = 'hidden'
..style.position = 'absolute'
..style.width = '100%'
..style.overflow = 'auto'
..innerHTML = currentContent.toJS;
web.document.body?.append(ghost);
await Future.delayed(Duration(milliseconds: 10));
final heightOfEmail = ghost.scrollHeight;
ghost.remove();
final HTMLsnippet = web.document.createElement('div') as web.HTMLDivElement
..id = viewTypeId
..innerHTML = widget
.emailHTML.toJS; // temporarily index because it has to do all of them
HTMLsnippet.style
..width = '100%'
..height = '${heightOfEmail}px'
..overflow = 'auto'
..scrollBehavior = 'smooth';
ui.platformViewRegistry.registerViewFactory(
viewTypeId,
(int viewId) => HTMLsnippet,
);
this.viewTypeIDs = viewTypeId;
this.heightOFViewtype = heightOfEmail;
print(viewTypeIDs);
}
@override
Widget build(BuildContext context) {
return _isLoaded
? Scaffold(
appBar: AppBar(title: Text(widget.email.subject)),
body: Stack(
children: [
Column(
children: [
EmailToolbar(
onButtonPressed: () => {},
onJumpToSpan: _scrollToNumber),
Row(
// title of email
children: [
Text(
widget.email.subject,
style: TextStyle(fontSize: 30),
),
],
),
Row(
children: [
Text(
'from ${widget.email.name}',
style: TextStyle(fontSize: 18),
),
Text(
'<${widget.email.from}>',
style: TextStyle(fontSize: 18),
),
Spacer(),
Text(
'${widget.email.date}',
textAlign: TextAlign.right,
)
],
),
// TODO: make a case where if one of these is the user's email it just says me :)))))
Row(
children: [
Text(
'to ${widget.email.to.toString()}',
style: TextStyle(fontSize: 15),
)
],
),
Expanded(
// child: SizedBox(
// height: heightOFViewtype.toDouble(),
child: HtmlElementView(
key: UniqueKey(), viewType: this.viewTypeIDs,
// ),
))
],
),
],
),
)
: const Center(
child: CircularProgressIndicator(),
);
}
}

View File

@ -4,19 +4,12 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'collapsableEmails.dart';
import 'structs.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:ui_web' as ui;
import 'augment.dart';
// import 'dart:html' as html;
// import 'dart:js' as js;
import 'package:web/web.dart' as web;
import 'dart:js_interop' as js;
class ApiService {
static String ip = "";
@ -422,237 +415,4 @@ class ApiService {
// return [];
// }
}
class EmailView extends StatefulWidget {
final List<String> emailContent;
final String from;
final String name;
final String to;
final String subject;
final String date;
final String id;
final List<String> messages;
const EmailView({
Key? key,
required this.emailContent,
required this.from,
required this.name,
required this.to,
required this.subject,
required this.date,
required this.id,
required this.messages,
}) : super(key: key);
@override
_EmailViewState createState() => _EmailViewState();
}
class _EmailViewState extends State<EmailView> {
//html css rendering thing
late Key iframeKey;
late String currentContent;
late String viewTypeId; //make this a list too???
Future<List<Map<String, dynamic>>>? _markerPositionsFuture;
// TextEditingController _jumpController = TextEditingController();
final hardcodedMarkers = [
{'id': 'marker1', 'x': 50, 'y': 100},
{'id': 'marker2', 'x': 150, 'y': 200},
{'id': 'marker3', 'x': 250, 'y': 300},
];
@override
void initState() {
super.initState();
print("thread id? ${widget.id}");
List<String> currentContent = widget
.emailContent; //html of the email/ actually entire thread, gives me little space to play in between
// i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse
// _registerViewFactory(currentContent);
}
// void _registerViewFactory(List<String> currentContent) { // i think this doesnt work anymore
// setState(() { //update to do item per item
// // each item to have itsviewtype ID
// // is this necessarey here??
// //could just move to collapsable
// viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
// final emailHTML = web.document.createElement('div') as web.HTMLDivElement
// ..id = viewTypeId
// ..innerHTML = currentContent[0].toJS; // temporarily index because it has to do all of them
// emailHTML.style
// ..width = '100%'
// ..height = '100%'
// ..overflow = 'auto'
// ..scrollBehavior = 'smooth';
// ui.platformViewRegistry.registerViewFactory(
// viewTypeId,
// (int viewId) => emailHTML,
// );
// });
// }
void _scrollToNumber(String spanId) {
AugmentClasses.handleJump(spanId);
}
// TODO: void _invisibility(String ) //to make purple numbers not visible
@override
Widget build(BuildContext context) {
// print("thread id ${widget.id}");
ApiService.currThreadID = widget.id;
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Stack(
children: [
Column(
children: [
EmailToolbar(
onJumpToSpan: _scrollToNumber,
onButtonPressed: () => {},
// AugmentClasses.handleJump(viewTypeId, '1');
// print("button got pressed?");
// _registerViewFactory(r"""
// <h1>Welcome to My Website</h1>
// <p>This is a simple HTML page.</p>
// <h2>What is HTML?</h2>
// <p>HTML (HyperText Markup Language) is the most basic building~ block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
// <h3>Here's a simple list:</h3>
// <ul>
// <li>HTML elements are the building blocks of HTML pages</li>
// <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
// <li>CSS is used with HTML to style pages</li>
// </ul>
// <p>Copyright © 2023</p>
// """);
// print("change");
// widget.emailContent = r"
//
),
Row(
// title of email
children: [
Text(
widget.subject,
style: TextStyle(fontSize: 30),
),
],
),
Row(
children: [
Text(
'from ${widget.name}',
style: TextStyle(fontSize: 18),
),
Text(
'<${widget.from}>',
style: TextStyle(fontSize: 18),
),
Spacer(),
Text(
'${widget.date}',
textAlign: TextAlign.right,
)
],
),
// TODO: make a case where if one of these is the user's email it just says me :)))))
Row(
children: [
Text(
'to ${widget.to.toString()}',
style: TextStyle(fontSize: 15),
)
],
),
Expanded(
child: CollapsableEmails(
//change here
thread: widget.messages, //this wont work in serializable
threadHTML: widget.emailContent,
threadIDs: widget.id,
),
),
// Expanded(
// child: HtmlElementView(
// key: UniqueKey(),
// viewType: viewTypeId,
// ),
// ),
],
),
// Overlay widgets dynamically based on marker positions
// FutureBuilder<List<Map<String, dynamic>>>(
// future: _markerPositionsFuture,
// builder: (context, snapshot) {
// print("FutureBuilder state: ${snapshot.connectionState}");
// if (snapshot.connectionState == ConnectionState.waiting) {
// return Center(child: CircularProgressIndicator());
// }
// if (snapshot.hasError) {
// print("Error in FutureBuilder: ${snapshot.error}");
// return Center(child: Text('error loading markers'));
// }
// if (snapshot.hasData && snapshot.data != null) {
// final markers = snapshot.data!;
// return Stack(
// children: markers.map((marker) {
// return Positioned(
// left: marker['x'].toDouble(),
// top: marker['y'].toDouble(),
// child: GestureDetector(
// onTap: () {
// print('Tapped on ${marker['id']}');
// },
// child: Container(
// width: 50,
// height: 50,
// color: Colors.red,
// child: Center(
// child: Text(
// marker['id'],
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// );
// }).toList(),
// );
// }
// return SizedBox.shrink(); // No markers found
// },
// ),
// Red widget overlay
// Positioned(
// left: 8, // Adjust based on your desired position
// top: 100 + 44 + 5, // Adjust based on your desired position
// child: IgnorePointer(
// ignoring: true, // Ensures the iframe remains interactive
// child: Container(
// color: Colors.red,
// width: 100,
// height: 50,
// child: Center(
// child: Text(
// 'Overlay',
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// ),
],
));
}
}
}

View File

@ -0,0 +1,7 @@
import 'structs.dart';
class Attachmentdownload {
Future<void> saveFile(AttachmentResponse attachment) async {
print("stub attachment download");
}
}

View File

@ -1,15 +1,3 @@
import 'dart:html' as html;
import 'package:web/web.dart' as web;
import 'dart:io';
import 'structs.dart';
import 'package:file_saver/file_saver.dart';
class Attachmentdownload {
Future<void> saveFile(AttachmentResponse attachment) async {
await FileSaver.instance.saveFile(
name: attachment.name.toString().substring(0, attachment.name.toString().lastIndexOf('.')),
bytes: attachment.data,
ext: attachment.name.toString().substring(attachment.name.toString().lastIndexOf('.')+1)
);
}
}
export 'attachamentDownloadStub.dart'
if (dart.library.io) 'attachmentDownloadAndroid.dart';
// if (dart.library.js_interop) 'attachmentDownloadWeb.dart';

View File

@ -0,0 +1,7 @@
import 'structs.dart';
class Attachmentdownload {
Future<void> saveFile(AttachmentResponse attachment) async {
print("android attachment download");
}
}

View File

@ -0,0 +1,12 @@
// import 'structs.dart';
// import 'package:file_saver/file_saver.dart';
// class Attachmentdownload {
// Future<void> saveFile(AttachmentResponse attachment) async {
// await FileSaver.instance.saveFile(
// name: attachment.name.toString().substring(0, attachment.name.toString().lastIndexOf('.')),
// bytes: attachment.data,
// ext: attachment.name.toString().substring(attachment.name.toString().lastIndexOf('.')+1)
// );
// }
// }

View File

@ -1,103 +1,3 @@
import "dart:typed_data";
import "package:crab_ui/attachmentDownload.dart";
import "package:crab_ui/structs.dart";
import "package:flutter/material.dart";
import 'package:pdfrx/pdfrx.dart';
import 'package:photo_view/photo_view.dart';
class AttachmentWidget extends StatelessWidget {
final AttachmentResponse attachment;
AttachmentWidget({required this.attachment});
Widget attachmentViewer(AttachmentResponse att) {
String extension = att.name
.toString()
.substring(att.name.toString().indexOf(".") + 1)
.toLowerCase();
if (extension == "jpg" || extension == "png") {
return Image.memory(att.data);
} else if (extension == "pdf") {
return PdfViewer.data(Uint8List.fromList(att.data),
sourceName: att.name,
params: PdfViewerParams(
enableTextSelection: true,
scrollByMouseWheel: 0.5,
annotationRenderingMode:
PdfAnnotationRenderingMode.annotationAndForms,
));
}
return Center(
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xff6C63FF),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"No preview available",
style: TextStyle(
color: Colors.white, fontSize: 18, decoration: TextDecoration.none),
),
SizedBox(
height: 5,
),
GestureDetector(
child: ElevatedButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("Download", style: TextStyle(color: Color(0xff2c3e50)),),
Icon(Icons.download,
color: Color(0xffb0b0b0),),
]),
onPressed: () => Attachmentdownload().saveFile(att),
)),
]),
));
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black38,
child: Stack(children: <Widget>[
Container(
color: Colors.white,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 20, 0, 10),
child: Column(
children: [
Row(
children: [
CloseButton(onPressed: () => {Navigator.pop(context)}),
Text(
attachment.name
.toString(), //its alr a string but incase ¯\()/¯ //update: i did that everywhere lol
style: TextStyle(
color: Colors.black,
fontSize: 20,
decoration: TextDecoration
.none), //TODO: personalize your fonts
),
],
),
Expanded(
child: attachmentViewer(attachment),
)
],
),
),
),
]));
}
}
export 'attachmentWidgetStub.dart'
if (dart.library.js_interop) 'attachmentWidgetWeb.dart'
if (dart.library.io) 'attachmentWidgetAndroid.dart';

View File

@ -0,0 +1,16 @@
import "package:crab_ui/structs.dart";
import "package:flutter/material.dart";
class AttachmentWidget extends StatelessWidget {
final AttachmentResponse attachment;
AttachmentWidget({required this.attachment});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Text("PDF EVENTUALLY ANDROID")
);
}
}

View File

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
import 'structs.dart';
class AttachmentWidget extends StatelessWidget{
final AttachmentResponse attachment;
AttachmentWidget({required this.attachment});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Text("PDF EVENTUALLY, STUB")
);
}
}

View File

@ -0,0 +1,101 @@
import "dart:typed_data";
import "package:crab_ui/attachmentDownload.dart";
import "package:crab_ui/structs.dart";
import "package:flutter/material.dart";
import 'package:pdfrx/pdfrx.dart';
class AttachmentWidget extends StatelessWidget {
final AttachmentResponse attachment;
AttachmentWidget({required this.attachment});
Widget attachmentViewer(AttachmentResponse att) {
String extension = att.name
.toString()
.substring(att.name.toString().indexOf(".") + 1)
.toLowerCase();
if (extension == "jpg" || extension == "png") {
return Image.memory(att.data);
} else if (extension == "pdf") {
return PdfViewer.data(Uint8List.fromList(att.data),
sourceName: att.name,
params: PdfViewerParams(
enableTextSelection: true,
scrollByMouseWheel: 0.5,
annotationRenderingMode:
PdfAnnotationRenderingMode.annotationAndForms,
));
}
return Center(
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xff6C63FF),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"No preview available",
style: TextStyle(
color: Colors.white, fontSize: 18, decoration: TextDecoration.none),
),
SizedBox(
height: 5,
),
GestureDetector(
child: ElevatedButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("Download", style: TextStyle(color: Color(0xff2c3e50)),),
Icon(Icons.download,
color: Color(0xffb0b0b0),),
]),
onPressed: () => Attachmentdownload().saveFile(att),
)),
]),
));
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black38,
child: Stack(children: <Widget>[
Container(
color: Colors.white,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 20, 0, 10),
child: Column(
children: [
Row(
children: [
CloseButton(onPressed: () => {Navigator.pop(context)}),
Text(
attachment.name
.toString(), //its alr a string but incase ¯\()/¯ //update: i did that everywhere lol
style: TextStyle(
color: Colors.black,
fontSize: 20,
decoration: TextDecoration
.none), //TODO: personalize your fonts
),
],
),
Expanded(
child: attachmentViewer(attachment),
)
],
),
),
),
]));
}
}

View File

@ -1,14 +1,7 @@
// import 'dart:ffi';
import 'package:crab_ui/api_service.dart';
import 'package:crab_ui/attachmentDownload.dart';
import 'package:crab_ui/structs.dart';
import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
// import 'dart:html' as html;
// import 'dart:js' as js;
import 'package:web/web.dart' as web;
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'attachmentWidget.dart';

View File

@ -1,132 +1,3 @@
import 'dart:js_interop';
import 'package:web/web.dart' as web;
import 'package:flutter/material.dart';
import 'dart:ui_web' as ui;
import 'api_service.dart';
import 'structs.dart';
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
final String threadIDs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();
}
class _CollapsableEmailsState extends State<CollapsableEmails> {
List<String> emailsHTML = []; //html of the emails in the thread
// build attachments with the forldar name and id
Set<int> _expandedEmails = {}; //open emails
List viewtypeIDs = []; //IDs of the viewtypes, order matters
List heightOfViewTypes = []; //the height of each viewtype
List<SerializableMessage> emailsInThread = [];
bool _isLoaded = false;
@override
void initState() {
// TODO: implement initState
super.initState();
_registerViewFactory(widget.threadHTML);
_serializableData(widget.threadIDs);
}
void _registerViewFactory(List<String> currentContent) async {
// setState(() { //update to do item per item
// each item to have itsviewtype ID
// is this necessarey here??
//could just move to collapsable
for (var emailHTML in widget.threadHTML) {
String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
final ghost = web.document.createElement('div') as web.HTMLDivElement
..style.visibility = 'hidden'
..style.position = 'absolute'
..style.width = '100%'
..style.overflow = 'auto'
..innerHTML = emailHTML
.toJS; // temporarily index because it has to do all of them
web.document.body?.append(ghost);
await Future.delayed(Duration(milliseconds: 10));
final heightOfEmail = ghost.scrollHeight;
ghost.remove();
final HTMLsnippet = web.document.createElement('div')
as web.HTMLDivElement
..id = viewTypeId
..innerHTML = emailHTML
.toJS; // temporarily index because it has to do all of them
HTMLsnippet.style
..width = '100%'
..height = '${heightOfEmail}px'
..overflow = 'auto'
..scrollBehavior = 'smooth';
ui.platformViewRegistry.registerViewFactory(
viewTypeId,
(int viewId) => HTMLsnippet,
);
viewtypeIDs.add(viewTypeId);
heightOfViewTypes.add(heightOfEmail);
}
}
void _serializableData(String threadID) async {
emailsInThread = await ApiService().threadsInSerializable(threadID);
print("done thread serializable");
if (!mounted) return;
setState(() {
_isLoaded = true;
});
}
@override
Widget build(BuildContext context) {
return _isLoaded
?Column(children: [
Expanded(
child: ListView.builder(
itemCount: widget.thread.length,
itemBuilder: (context, index) {
final isExpanded =
_expandedEmails.contains(index); //check if email is expanded
return Column(
children: [
ListTile(
title: Text(emailsInThread[index].from),
trailing: Text(emailsInThread[index].date),
onTap: () {
setState(() {
if (isExpanded) {
_expandedEmails.remove(index);
} else {
_expandedEmails.add(index);
}
});
},
),
if (isExpanded)
// if(viewtypeIDs[index] == null || heightOfViewTypes[index] == null)
// const SizedBox(height: 100, child: Center(child: CircularProgressIndicator())),
SizedBox(
height: heightOfViewTypes[index].toDouble(),
child: HtmlElementView(
key: UniqueKey(), viewType: viewtypeIDs[index]),
),
Divider(),
],
);
},
),
)
]): const Center(child:CircularProgressIndicator());
}
}
export 'collapsableEmailsStub.dart'
if (dart.library.io) 'collapsableEmailsAndroid.dart'
if (dart.library.js_interop) 'collapsableEmailsWeb.dart';

View File

@ -0,0 +1,48 @@
import 'structs.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
final String threadIDs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();
}
class _CollapsableEmailsState extends State<CollapsableEmails> {
List<String> emailsHTML = []; //html of the emails in the thread
// build attachments with the forldar name and id
Set<int> _expandedEmails = {}; //open emails
List viewtypeIDs = []; //IDs of the viewtypes, order matters
List heightOfViewTypes = []; //the height of each viewtype
List<SerializableMessage> emailsInThread = [];
bool _isLoaded = false;
@override
void initState() {
super.initState();
//html
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
HtmlWidget(
widget.threadHTML[0],
// renderMode: RenderMode.listView,
)
]
)
);
}
}

View File

@ -0,0 +1,23 @@
import 'structs.dart';
import 'package:flutter/material.dart';
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
final String threadIDs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();
}
class _CollapsableEmailsState extends State<CollapsableEmails> {
@override
Widget build(BuildContext context) {
return Scaffold(body: Text("collapsable stud"));
}
}

View File

@ -0,0 +1,255 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:web/web.dart' as web;
import 'package:flutter/material.dart';
import 'dart:ui_web' as ui;
import 'api_service.dart';
import 'structs.dart';
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
final String threadIDs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();
}
class _CollapsableEmailsState extends State<CollapsableEmails> {
List<String> emailsHTML = []; //html of the emails in the thread
// build attachments with the forldar name and id
Set<int> _expandedEmails = {}; //open emails
List viewtypeIDs = []; //IDs of the viewtypes, order matters
List heightOfViewTypes = []; //the height of each viewtype
List<SerializableMessage> emailsInThread = [];
bool _isLoaded = false;
static bool _isListenerRegistered = false;
static bool left = true;
static bool right = true;
web.EventListener? _listener;
List<String> hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"];
List<String> tagsCollected = [];
@override
void initState() {
super.initState();
_registerViewFactory(widget.threadHTML);
_serializableData(widget.threadIDs); // this
_keyListener();
}
// @override
// void dispose() {
// if (_listener != null) {
// web.window.document.removeEventListener('keydown', _listener!);
// }
// super.dispose();
// }
void _registerViewFactory(List<String> currentContent) async {
// setState(() { //update to do item per item
// each item to have itsviewtype ID
// is this necessarey here??
//could just move to collapsable
for (var emailHTML in widget.threadHTML) {
String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
final ghost = web.document.createElement('div') as web.HTMLDivElement
..style.visibility = 'hidden'
..style.position = 'absolute'
..style.width = '100%'
..style.overflow = 'auto'
..innerHTML = emailHTML
.toJS; // temporarily index because it has to do all of them
web.document.body?.append(ghost);
await Future.delayed(Duration(milliseconds: 10));
final heightOfEmail = ghost.scrollHeight;
ghost.remove();
final HTMLsnippet = web.document.createElement('div')
as web.HTMLDivElement
..id = viewTypeId
..innerHTML = emailHTML
.toJS; // temporarily index because it has to do all of them
HTMLsnippet.style
..width = '100%'
..height = '${heightOfEmail}px'
..overflow = 'auto'
..scrollBehavior = 'smooth';
ui.platformViewRegistry.registerViewFactory(
viewTypeId,
(int viewId) => HTMLsnippet,
);
viewtypeIDs.add(viewTypeId);
heightOfViewTypes.add(heightOfEmail);
}
}
void _serializableData(String threadID) async {
emailsInThread = await ApiService().threadsInSerializable(threadID);
print("done thread serializable");
if (!mounted) return;
setState(() {
_isLoaded = true;
});
}
void handleKeyDown(web.Event event) {
final keyEvent = event as web.KeyboardEvent;
if (keyEvent.key == 'G') {
print('You pressed the "G" key!');
final rightPurpleNums = web.document.getElementsByClassName("right");
_CollapsableEmailsState.right = !_CollapsableEmailsState.right;
final newOpacity = _CollapsableEmailsState.right ? '1.0' : '0.0';
for (int i = 0; i < rightPurpleNums.length; i++) {
final currentElement = rightPurpleNums.item(i) as web.HTMLElement;
currentElement.style.opacity = newOpacity;
}
} else if (keyEvent.key == 'H') {
print('You pressed the "H" key!');
final leftPurpleNums = web.document.getElementsByClassName("left");
_CollapsableEmailsState.left = !_CollapsableEmailsState.left;
final newOpacity = _CollapsableEmailsState.left ? '1.0' : '0.0';
for (int i = 0; i < leftPurpleNums.length; i++) {
final currentElement = leftPurpleNums.item(i) as web.HTMLElement;
currentElement.style.opacity = newOpacity;
}
} else if (keyEvent.key == 'm') {
print("you pressed 'm'");
final purpleNums = web.document.getElementsByClassName("purplenumber");
_CollapsableEmailsState.left = true;
_CollapsableEmailsState.right = true;
for (int i = 0; i < purpleNums.length; i++) {
final currentElement = purpleNums.item(i) as web.HTMLElement;
currentElement.style.opacity = '1.0';
}
} else if (keyEvent.key == 'n') {
print("you pressed 'n'");
final purpleNums = web.document.getElementsByClassName("purplenumber");
_CollapsableEmailsState.left = false;
_CollapsableEmailsState.right = false;
for (int i = 0; i < purpleNums.length; i++) {
final currentElement = purpleNums.item(i) as web.HTMLElement;
currentElement.style.opacity = '0.0';
}
} else if (keyEvent.key == 'w') {
print("you pressed 'w'");
getTopLevel();
}
}
void _keyListener() {
if (_isListenerRegistered) return;
_isListenerRegistered = true;
// Convert the top-level function to JS-compatible
_listener = handleKeyDown.toJS;
web.window.document.addEventListener('keydown', _listener!);
}
void getTopLevel() {
print("started top");
int highest = 0;
AugmentTree zoomTreeRoot = AugmentTree();
// zoomTreeRoot.data = emailsHTML[0]; // whole thing
while (highest < hirarchy.length - 1) {
var highestElement = web.document.querySelectorAll(hirarchy[highest]);
print("nodelist $highestElement");
if (highestElement.isNull || highestElement.length == 0) {
//from h1, h2, h3, ..., p.
highest += 1;
print(hirarchy[highest]);
} else {
AugmentTree newLevel = AugmentTree(); // list of children of each level
for (int i = 0; i < highestElement.length; i++) {
print(highestElement.item(i)?.textContent);
tagsCollected
.add(highestElement.item(i)?.textContent ?? "nameless subtitle");
newLevel.children
.add(highestElement.item(i)?.textContent ?? "nameless subtitle");
}
// traverse to last node and add new level to it
// next
if (zoomTreeRoot.node == null) {
zoomTreeRoot.node = newLevel;
} else {
AugmentTree temp = zoomTreeRoot;
while (true) {
//get to the last and assign node = last node
if (temp.node != null) {
temp = temp.node!;
} else {
temp.node = newLevel;
break;
}
}
}
highest += 1;
}
}
print("out safely");
print(tagsCollected); //instead of a list make a tree
print(zoomTreeRoot.children);
print(zoomTreeRoot.node?.children);
print(zoomTreeRoot.node?.node?.children);
print(zoomTreeRoot.node?.node?.node?.children);
}
@override
Widget build(BuildContext context) {
return _isLoaded
? Column(children: [
Expanded(
child: ListView.builder(
itemCount: widget.thread.length,
itemBuilder: (context, index) {
final isExpanded = _expandedEmails
.contains(index); //check if email is expanded
return Column(
children: [
ListTile(
title: Text(emailsInThread[index].from),
trailing: Text(emailsInThread[index].date),
onTap: () {
setState(() {
if (isExpanded) {
_expandedEmails.remove(index);
} else {
_expandedEmails.add(index);
}
});
},
),
if (isExpanded)
SizedBox(
height: heightOfViewTypes[index].toDouble(),
child: HtmlElementView(
key: UniqueKey(), viewType: viewtypeIDs[index]),
),
Divider(),
],
);
},
),
)
])
: const Center(child: CircularProgressIndicator());
}
}

View File

@ -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});

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'api_service.dart';
import 'structs.dart';
import 'emailView.dart';
class EmailListScreen extends StatelessWidget {
final List<GetThreadResponse> emails;

3
lib/emailView.dart Normal file
View File

@ -0,0 +1,3 @@
export 'emailViewStub.dart'
if (dart.library.io) 'emailViewAndroid.dart'
if (dart.library.js_interop) 'emailViewWeb.dart';

95
lib/emailViewAndroid.dart Normal file
View File

@ -0,0 +1,95 @@
import 'package:crab_ui/collapsableEmailsAndroid.dart';
import 'package:flutter/material.dart';
// import 'dart:ui_web' as ui;
// import 'augment.dart';
// // import 'dart:js_interop' as js; //eventually for manipulating css
// import 'package:pointer_interceptor/pointer_interceptor.dart';
// import 'collapsableEmails.dart';
// import 'api_service.dart';
class EmailView extends StatefulWidget {
final List<String> emailContent;
final String from;
final String name;
final String to;
final String subject;
final String date;
final String id;
final List<String> messages;
const EmailView({
Key? key,
required this.emailContent,
required this.from,
required this.name, // tf is name
required this.to,
required this.subject,
required this.date,
required this.id,
required this.messages,
}) : super(key: key);
@override
_EmailViewState createState() => _EmailViewState();
}
class _EmailViewState extends State<EmailView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
widget.subject,
style: TextStyle(fontSize: 15),
overflow: TextOverflow.visible,
softWrap: true,
),
),
],
),
Row(
children: [
Text(
'from ${widget.name}',
style: TextStyle(fontSize: 8),
),
Text(
'<${widget.from}>',
style: TextStyle(fontSize: 8),
),
Spacer(),
Text(
widget.date,
textAlign: TextAlign.right,
)
],
),
Row(
children: [
Text(
'to ${widget.to.toString()}',
style: TextStyle(fontSize: 8),
)
],
),
Expanded(
child: CollapsableEmails(
thread: widget.messages,
threadHTML: widget.emailContent,
threadIDs: widget.id,
),
),
],
),
)
);
}
}

39
lib/emailViewStub.dart Normal file
View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class EmailView extends StatefulWidget {
final List<String> emailContent;
final String from;
final String name;
final String to;
final String subject;
final String date;
final String id;
final List<String> messages;
const EmailView({
Key? key,
required this.emailContent,
required this.from,
required this.name,
required this.to,
required this.subject,
required this.date,
required this.id,
required this.messages,
}) : super(key: key);
@override
_EmailViewState createState() => _EmailViewState();
}
class _EmailViewState extends State<EmailView> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(" emailview stub, not supported")
)
);
}
}

241
lib/emailViewWeb.dart Normal file
View File

@ -0,0 +1,241 @@
import 'package:flutter/material.dart';
import 'dart:ui_web' as ui;
import 'augment.dart';
// import 'dart:js_interop' as js; //eventually for manipulating css
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'collapsableEmails.dart';
import 'api_service.dart';
class EmailView extends StatefulWidget {
final List<String> emailContent;
final String from;
final String name;
final String to;
final String subject;
final String date;
final String id;
final List<String> messages;
const EmailView({
Key? key,
required this.emailContent,
required this.from,
required this.name,
required this.to,
required this.subject,
required this.date,
required this.id,
required this.messages,
}) : super(key: key);
@override
_EmailViewState createState() => _EmailViewState();
}
class _EmailViewState extends State<EmailView> {
//html css rendering thing
late Key iframeKey;
late String currentContent;
late String viewTypeId; //make this a list too???
Future<List<Map<String, dynamic>>>? _markerPositionsFuture;
// TextEditingController _jumpController = TextEditingController();
final hardcodedMarkers = [
{'id': 'marker1', 'x': 50, 'y': 100},
{'id': 'marker2', 'x': 150, 'y': 200},
{'id': 'marker3', 'x': 250, 'y': 300},
];
@override
void initState() {
super.initState();
print("thread id? ${widget.id}");
List<String> currentContent = widget
.emailContent; //html of the email/ actually entire thread, gives me little space to play in between
// i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse
// _registerViewFactory(currentContent);
}
// void _registerViewFactory(List<String> currentContent) { // i think this doesnt work anymore
// setState(() { //update to do item per item
// // each item to have itsviewtype ID
// // is this necessarey here??
// //could just move to collapsable
// viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
// final emailHTML = web.document.createElement('div') as web.HTMLDivElement
// ..id = viewTypeId
// ..innerHTML = currentContent[0].toJS; // temporarily index because it has to do all of them
// emailHTML.style
// ..width = '100%'
// ..height = '100%'
// ..overflow = 'auto'
// ..scrollBehavior = 'smooth';
// ui.platformViewRegistry.registerViewFactory(
// viewTypeId,
// (int viewId) => emailHTML,
// );
// });
// }
void _scrollToNumber(String spanId) {
AugmentClasses.handleJump(spanId);
}
// TODO: void _invisibility(String ) //to make purple numbers not visible
@override
Widget build(BuildContext context) {
// print("thread id ${widget.id}");
ApiService.currThreadID = widget.id;
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Stack(
children: [
Column(
children: [
EmailToolbar(
onJumpToSpan: _scrollToNumber,
onButtonPressed: () => {},
// AugmentClasses.handleJump(viewTypeId, '1');
// print("button got pressed?");
// _registerViewFactory(r"""
// <h1>Welcome to My Website</h1>
// <p>This is a simple HTML page.</p>
// <h2>What is HTML?</h2>
// <p>HTML (HyperText Markup Language) is the most basic building~ block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
// <h3>Here's a simple list:</h3>
// <ul>
// <li>HTML elements are the building blocks of HTML pages</li>
// <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
// <li>CSS is used with HTML to style pages</li>
// </ul>
// <p>Copyright © 2023</p>
// """);
// print("change");
// widget.emailContent = r"
//
),
Row(
// title of email
children: [
Text(
widget.subject,
style: TextStyle(fontSize: 30),
),
],
),
Row(
children: [
Text(
'from ${widget.name}',
style: TextStyle(fontSize: 18),
),
Text(
'<${widget.from}>',
style: TextStyle(fontSize: 18),
),
Spacer(),
Text(
'${widget.date}',
textAlign: TextAlign.right,
)
],
),
// TODO: make a case where if one of these is the user's email it just says me :)))))
Row(
children: [
Text(
'to ${widget.to.toString()}',
style: TextStyle(fontSize: 15),
)
],
),
Expanded(
child: CollapsableEmails(
//change here
thread: widget.messages, //this wont work in serializable
threadHTML: widget.emailContent,
threadIDs: widget.id,
),
),
// Expanded(
// child: HtmlElementView(
// key: UniqueKey(),
// viewType: viewTypeId,
// ),
// ),
],
),
// Overlay widgets dynamically based on marker positions
// FutureBuilder<List<Map<String, dynamic>>>(
// future: _markerPositionsFuture,
// builder: (context, snapshot) {
// print("FutureBuilder state: ${snapshot.connectionState}");
// if (snapshot.connectionState == ConnectionState.waiting) {
// return Center(child: CircularProgressIndicator());
// }
// if (snapshot.hasError) {
// print("Error in FutureBuilder: ${snapshot.error}");
// return Center(child: Text('error loading markers'));
// }
// if (snapshot.hasData && snapshot.data != null) {
// final markers = snapshot.data!;
// return Stack(
// children: markers.map((marker) {
// return Positioned(
// left: marker['x'].toDouble(),
// top: marker['y'].toDouble(),
// child: GestureDetector(
// onTap: () {
// print('Tapped on ${marker['id']}');
// },
// child: Container(
// width: 50,
// height: 50,
// color: Colors.red,
// child: Center(
// child: Text(
// marker['id'],
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// );
// }).toList(),
// );
// }
// return SizedBox.shrink(); // No markers found
// },
// ),
// Red widget overlay
// Positioned(
// left: 8, // Adjust based on your desired position
// top: 100 + 44 + 5, // Adjust based on your desired position
// child: IgnorePointer(
// ignoring: true, // Ensures the iframe remains interactive
// child: Container(
// color: Colors.red,
// width: 100,
// height: 50,
// child: Center(
// child: Text(
// 'Overlay',
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// ),
],
));
}
}

View File

@ -2,7 +2,7 @@ import 'package:crab_ui/sonicEmailView.dart';
import 'folder_drawer.dart';
import 'structs.dart';
import 'package:flutter/widgets.dart';
// import 'package:flutter/widgets.dart';
import 'api_service.dart';
import 'package:flutter/material.dart';
import 'email.dart';
@ -266,40 +266,45 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 800,
height: 40,
child: TextField(
decoration: InputDecoration(
hintText: 'Search...',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.search),
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 800,
),
child: SizedBox(
height: 40,
child: TextField(
decoration: InputDecoration(
hintText: 'Search...',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.search),
),
onSubmitted: (value) {
if (value.isNotEmpty) {
_performSearch(value, _selectedOption);
}
//this is the input box i mentioned
// if (value == '') {
// setState(() {
// querySearches = false;
// });
// }
// Future<List<String>> results = apiService
// .sonicSearch('INBOX', 20, 0, value);
// // print(value);
// print(results);
// setState(() {
// querySearches = true;
// });
},
),
),
onSubmitted: (value) {
if (value.isNotEmpty) {
_performSearch(value, _selectedOption);
}
//this is the input box i mentioned
// if (value == '') {
// setState(() {
// querySearches = false;
// });
// }
// Future<List<String>> results = apiService
// .sonicSearch('INBOX', 20, 0, value);
// // print(value);
// print(results);
// setState(() {
// querySearches = true;
// });
},
),
),
SizedBox(
width: 16,
width: 8,
),
Container(
width: 80,
height: 40,
child: ElevatedButton(
onPressed: _showOptionsSearchDialog,

View File

@ -1,145 +1,3 @@
import 'package:crab_ui/augment.dart';
import 'package:web/web.dart' as web;
import 'dart:ui_web' as ui;
import 'dart:js_interop';
import 'structs.dart';
import 'package:flutter/material.dart';
class SonicEmailView extends StatefulWidget {
SerializableMessage email;
String emailHTML;
SonicEmailView({required this.email, required this.emailHTML});
@override
_SonicEmailViewState createState() => _SonicEmailViewState();
}
class _SonicEmailViewState extends State<SonicEmailView> {
String viewTypeIDs = "";
int heightOFViewtype = 0;
bool _isLoaded = false;
void _scrollToNumber(String spanId) {
AugmentClasses.handleJump(spanId);
}
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
await _registerViewFactory(widget.emailHTML);
if (!mounted) return;
setState(() {
_isLoaded = true;
});
}
Future<void> _registerViewFactory(String currentContent) async {
// setState(() { //update to do item per item
// each item to have itsviewtype ID
// is this necessarey here??
//could just move to collapsable
// for (var emailHTML in widget.threadHTML) {
String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
final ghost = web.document.createElement('div') as web.HTMLDivElement
..style.visibility = 'hidden'
..style.position = 'absolute'
..style.width = '100%'
..style.overflow = 'auto'
..innerHTML = currentContent.toJS;
web.document.body?.append(ghost);
await Future.delayed(Duration(milliseconds: 10));
final heightOfEmail = ghost.scrollHeight;
ghost.remove();
final HTMLsnippet = web.document.createElement('div') as web.HTMLDivElement
..id = viewTypeId
..innerHTML = widget
.emailHTML.toJS; // temporarily index because it has to do all of them
HTMLsnippet.style
..width = '100%'
..height = '${heightOfEmail}px'
..overflow = 'auto'
..scrollBehavior = 'smooth';
ui.platformViewRegistry.registerViewFactory(
viewTypeId,
(int viewId) => HTMLsnippet,
);
this.viewTypeIDs = viewTypeId;
this.heightOFViewtype = heightOfEmail;
print(viewTypeIDs);
}
@override
Widget build(BuildContext context) {
return _isLoaded
? Scaffold(
appBar: AppBar(title: Text(widget.email.subject)),
body: Stack(
children: [
Column(
children: [
EmailToolbar(
onButtonPressed: () => {},
onJumpToSpan: _scrollToNumber),
Row(
// title of email
children: [
Text(
widget.email.subject,
style: TextStyle(fontSize: 30),
),
],
),
Row(
children: [
Text(
'from ${widget.email.name}',
style: TextStyle(fontSize: 18),
),
Text(
'<${widget.email.from}>',
style: TextStyle(fontSize: 18),
),
Spacer(),
Text(
'${widget.email.date}',
textAlign: TextAlign.right,
)
],
),
// TODO: make a case where if one of these is the user's email it just says me :)))))
Row(
children: [
Text(
'to ${widget.email.to.toString()}',
style: TextStyle(fontSize: 15),
)
],
),
Expanded(
// child: SizedBox(
// height: heightOFViewtype.toDouble(),
child: HtmlElementView(
key: UniqueKey(), viewType: this.viewTypeIDs,
// ),
))
],
),
],
),
)
: const Center(
child: CircularProgressIndicator(),
);
}
}
export 'SonicEmailViewStub.dart'
if (dart.library.js_interop) 'SonicEmailViewWeb.dart'
if (dart.library.io) 'SonicEmailViewAndroid.dart';

View File

@ -127,7 +127,8 @@ class AttachmentInfoList extends Iterable<AttachmentInfo> {
AttachmentInfoList(this._attachments);
factory AttachmentInfoList.fromJsonList(List<Map<String, dynamic>> jsonList) {
return AttachmentInfoList(jsonList.map((json) => AttachmentInfo.fromJson(json)).toList());
return AttachmentInfoList(
jsonList.map((json) => AttachmentInfo.fromJson(json)).toList());
}
@override
@ -143,6 +144,13 @@ class AttachmentResponse {
AttachmentResponse({required this.name, required this.data});
factory AttachmentResponse.fromJson(Map<String, dynamic> json) {
return AttachmentResponse(name: json["name"], data: Uint8List.fromList(List<int>.from(json["data"])));
return AttachmentResponse(
name: json["name"],
data: Uint8List.fromList(List<int>.from(json["data"])));
}
}
class AugmentTree {
List children = [];
AugmentTree? node;
}

File diff suppressed because it is too large Load Diff

View File

@ -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,7 @@ dependencies:
pdfrx: ^1.0.94
photo_view: ^0.15.0
web: ^1.1.1
flutter_widget_from_html: ^0.16.0
dev_dependencies:
flutter_test:
@ -39,6 +38,7 @@ dependency_overrides:
flutter_layout_grid: 2.0.7
flutter_math_fork: 0.7.2
flutter:
uses-material-design: true
assets: