diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a3abae1..dc1e9c7 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -27,7 +27,7 @@ jobs: KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }} - name: Build app - run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --target-platform android-arm64" + run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --release" env: KEYSTORE_PATH: ../../keystore.jks KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml deleted file mode 100644 index eeb4868..0000000 --- a/.github/workflows/ios.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: "Build iOS App" - -on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: - -jobs: - build-app: - runs-on: macos-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.4 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: gomuks/go.mod - - - name: Build app - run: | - flutter pub get - dart scripts/generate.dart - flutter pub run build_runner build - flutter build ios --release --no-codesign - - # Unsigned IPA: SideStore/AltStore re-sign this on-device with the - # user's own Apple ID at install time, so no certificate is needed here. - - name: Package unsigned IPA - run: | - mkdir Payload - cp -r build/ios/iphoneos/Runner.app Payload/ - zip -r Nexus.ipa Payload - - - name: Upload IPA artifact - uses: actions/upload-artifact@v6 - with: - name: Nexus.ipa - path: Nexus.ipa diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 45d04d7..8ed63cf 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,47 +1,32 @@ name: "Build MacOS App" on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: + # TODO: Uncomment this once the MacOS workflow actually works + # push: + # branches: ["main"] + # tags: ["*"] + workflow_dispatch: jobs: - build-app: - runs-on: macos-latest + build-app: + runs-on: macos-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.4 + - name: Lix GHA Installer Action + uses: samueldr/lix-gha-installer-action@v2026-02-22 + with: + extra_nix_config: experimental-features = nix-command flakes flake-self-attrs - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: gomuks/go.mod + - name: Build app + run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build macos --release" - - name: Build App - run: | - flutter pub get - dart scripts/generate.dart - flutter pub run build_runner build - flutter build macos --release - - - name: Create DMG - id: create-dmg - uses: L-Super/create-dmg-actions@28511e988b13ca34096d439159a66d468b8e724f - with: - dmg_name: nexus - src_dir: build/macos/Build/Products/Release/Nexus.app - - - name: Upload DMG - uses: actions/upload-artifact@v6 - with: - name: nexus.dmg - path: ${{ steps.create-dmg.outputs.dmg_path }} + - name: Upload installer artifact + uses: actions/upload-artifact@v6 + with: + name: App + path: build/macos/Build/Products/Release/Nexus.app \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4cb30cd..d9c8dd5 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,64 +1,65 @@ name: "Build EXE" on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: + push: + branches: ["main"] + tags: ["*"] + workflow_dispatch: jobs: - build-exe: - runs-on: windows-latest + build-exe: + runs-on: windows-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.4 - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: gomuks/go.mod + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 3.44.4 - - name: Setup MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - install: >- - mingw-w64-x86_64-gcc + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: gomuks/go.mod - - name: Go build - run: | - cd gomuks/pkg/ffi - go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: >- + mingw-w64-x86_64-gcc - - name: Build with Flutter - run: | - flutter pub get - dart scripts/generate.dart - flutter pub run build_runner build - flutter build windows --release + - name: Go build + run: | + cd gomuks/pkg/ffi + go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared - - name: Copy MinGW runtime DLLs - shell: msys2 {0} - run: | - cp /mingw64/bin/libgcc_s_seh-1.dll build/windows/x64/runner/Release/ - cp /mingw64/bin/libwinpthread-1.dll build/windows/x64/runner/Release/ - cp /mingw64/bin/libstdc++-6.dll build/windows/x64/runner/Release/ + - name: Build with Flutter + run: | + flutter pub get + dart scripts/generate.dart + flutter pub run build_runner build + flutter build windows --release - - name: Install Inno Setup - run: choco install innosetup -y + - name: Copy MinGW runtime DLLs + shell: msys2 {0} + run: | + cp /mingw64/bin/libgcc_s_seh-1.dll build/windows/x64/runner/Release/ + cp /mingw64/bin/libwinpthread-1.dll build/windows/x64/runner/Release/ + cp /mingw64/bin/libstdc++-6.dll build/windows/x64/runner/Release/ - - name: Build Inno Setup installer - run: iscc windows/installer.iss + - name: Install Inno Setup + run: choco install innosetup -y - - name: Upload installer artifact - uses: actions/upload-artifact@v6 - with: - name: windows-installer - path: windows/dist/Nexus-Setup.exe + - name: Build Inno Setup installer + run: iscc windows/installer.iss + + - name: Upload installer artifact + uses: actions/upload-artifact@v6 + with: + name: windows-installer + path: windows/dist/Nexus-Setup.exe \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 06dd51a..145276a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "gomuks"] path = gomuks url = https://github.com/gomuks/gomuks + branch = main diff --git a/.metadata b/.metadata index f12453b..1be1841 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "6b182d2c7585eba26d4edce0f97630effd256c33" + revision: "nixpkgs000000000000000000000000000000000" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 - base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 - - platform: ios - create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 - base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 + create_revision: nixpkgs000000000000000000000000000000000 + base_revision: nixpkgs000000000000000000000000000000000 + - platform: macos + create_revision: nixpkgs000000000000000000000000000000000 + base_revision: nixpkgs000000000000000000000000000000000 # User provided section diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index b83039c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "configurations": [ - { - "name": "Flutter", - "type": "dart", - "request": "launch", - "program": "lib/main.dart" - }, - { - "name": "Flutter (Headless)", - "type": "dart", - "request": "launch", - "program": "lib/main.dart", - "env": { "FLUTTER_HEADLESS": "1" } - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index fac2ed8..2ff533e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,8 +11,6 @@ "muks", "prefs", "unban", - "unifiedpush", - "unredact", - "webpush" + "unredact" ] } diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d9c7c9d..fb69a56 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -21,7 +21,7 @@ See [Effective Dart: Style](https://dart.dev/effective-dart/style) for general r Controllers live in `lib/controllers/` and provide a source that exposes data and logic via Riverpod providers, allowing other parts of the code to watch state changes with ref.watch (`ref.watch(MyController.provider)`), access the current value with ref.read (`ref.read(MyController.provider)`), and run helper methods on those classes using the notifier: ```dart -ref.read(MyController.provider.notifier).helperMethod() +ref.watch(MyController.provider.notifier).helperMethod() ``` We use an object oriented style for controllers, where `provider` is a static member on the controller class. E.g. diff --git a/README.md b/README.md index 1da8adb..57b42d8 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,12 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. ## Progress -- [x] Platform Support +- [ ] Platform Support - [x] Linux - [x] Windows - - Currently has some bugs, due to us not having any developers on Windows. If you want to fix these, get in contact with us though the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus). - [x] Android - - [x] MacOS - - [x] Unsigned .ipa - - [ ] App Store - - [x] iOS - - [x] Unsigned .ipa - - [ ] App Store + - [ ] MacOS ([#50](https://git.federated.nexus/Nexus/nexus/issues/50)) + - [ ] iOS - [ ] Web (may not be possible) - [x] Login (via OAuth) - [x] Rooms / Spaces @@ -55,10 +50,10 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [x] Per message profiles - [x] Attachments - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) - - [x] Tags + - [x] Mentions - [x] Users - [x] Rooms - - [x] Emojis + - [ ] Inline emoji picker (Putting this here since it'll be implemented the same way as mentions) - [ ] Custom emojis/stickers - [ ] GIFs using Gomuks' GIF proxies - [x] Receiving @@ -68,7 +63,9 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [x] URL Previews - [x] Replies - [x] Viewing - - [x] Jump to original message + - [ ] Jump to original message + - [x] In loaded timeline + - [ ] Out of loaded timeline - [x] Edits - [x] Attachments - [x] Unencrypted @@ -108,8 +105,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [x] Member list - [x] Sort by power level - [ ] Colors based off of power level -- [x] Notifications using UnifiedPush ([#35](https://git.federated.nexus/Nexus/nexus/issues/35)) -- [x] Notifications page +- [ ] Notifications using UnifiedPush ([#35](https://git.federated.nexus/Nexus/nexus/issues/35)) - [ ] Group calls using [MSC4195](https://github.com/matrix-org/matrix-spec-proposals/pull/4195) - [ ] Invites - [x] Settings @@ -119,13 +115,10 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. If you want to try out Nexus, grab one of the following artifacts from CI: - [Android APK](https://nightly.link/Henry-Hiles/nexus/workflows/android/main/APK.zip) -- [Unsigned iOS IPA](https://nightly.link/Henry-Hiles/nexus/workflows/ios/main/Nexus.ipa.zip) -- [Unsigned MacOS DMG](https://nightly.link/Henry-Hiles/nexus/workflows/macos/main/nexus.dmg.zip) - [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip) - Flatpak - [AArch64/Arm64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-aarch64.zip) - [x86_64/AMD64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-x86_64.zip) -- [NixOS Module](linux/nix/module.nix) ## Build it yourself @@ -133,8 +126,8 @@ If you want to try out Nexus, grab one of the following artifacts from CI: #### Linux -- On NixOS: Either use direnv and `direnv allow`, or `nix flake develop` -- On other distros: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues. +- With Nix: Either use direnv and `direnv allow`, or `nix flake develop` +- Without Nix: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues. #### Windows @@ -194,12 +187,6 @@ dart scripts/generate.dart > export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include" > ``` -Build webcrypto: - -```sh -flutter pub run webcrypto:setup -``` - Build generated files, and watch for new changes: ```sh diff --git a/altstore-source.json b/altstore-source.json deleted file mode 100644 index 4471fb6..0000000 --- a/altstore-source.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "Nexus", - "identifier": "nexus.federated.nexus.source", - "subtitle": "Nexus for iOS", - "description": "Nexus iOS builds for AltStore and SideStore.", - "iconURL": "https://git.federated.nexus/Nexus/nexus/raw/branch/main/assets/icon.png", - "website": "https://git.federated.nexus/Nexus/nexus", - "apps": [ - { - "name": "Nexus", - "bundleIdentifier": "nexus.federated.nexus", - "developerName": "Nexus", - "subtitle": "A Matrix client", - "localizedDescription": "Nexus for iOS. Unsigned builds, resigned at install by AltStore/SideStore.", - "iconURL": "https://git.federated.nexus/Nexus/nexus/raw/branch/main/assets/icon.png", - "category": "social", - "screenshots": [], - "versions": [] - } - ], - "news": [] -} diff --git a/analysis_options.yaml b/analysis_options.yaml index 00df0c4..a8b1078 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,18 +1,12 @@ analyzer: - errors: - invalid_annotation_target: ignore - avoid_print: ignore - exclude: - - "build/**" - - "**/*.g.dart" - - "**/*.freezed.dart" - - android/** - - ios/** - - web/** - - windows/** - - macos/** - - linux/** + errors: + invalid_annotation_target: ignore + avoid_print: ignore + exclude: + - "build/**" + - "**/*.g.dart" + - "**/*.freezed.dart" include: package:flutter_lints/flutter.yaml linter: - rules: - prefer_double_quotes: true + rules: + prefer_double_quotes: true \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..2e7fb67 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,77 @@ +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" +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + namespace = "nexus.federated.nexus" + ndkVersion = flutter.ndkVersion + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + defaultConfig { + applicationId = "nexus.federated.nexus" + minSdk = 29 + targetSdkVersion flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + signingConfigs { + release { + keyAlias "key" + def storePath = keystoreProperties['path'] ?: System.getenv("KEYSTORE_PATH") + storeFile storePath ? file(storePath) : null + keyPassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD") + storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD") + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + + debug { + applicationIdSuffix = ".debug" + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts deleted file mode 100644 index 9c0d96c..0000000 --- a/android/app/build.gradle.kts +++ /dev/null @@ -1,84 +0,0 @@ -import java.util.Properties - -plugins { - id("com.android.application") - id("dev.flutter.flutter-gradle-plugin") -} - -val keystoreProperties = Properties() -val keystorePropertiesFile = rootProject.file("key.properties") - -if (keystorePropertiesFile.exists()) { - keystorePropertiesFile.inputStream().use { - keystoreProperties.load(it) - } -} - -android { - namespace = "nexus.federated.nexus" - compileSdk = 36 - ndkVersion = flutter.ndkVersion - - compileOptions { - isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - } - - defaultConfig { - applicationId = "nexus.federated.nexus" - - minSdk = 29 - targetSdk = flutter.targetSdkVersion - - versionCode = flutter.versionCode - versionName = flutter.versionName - - multiDexEnabled = true - } - - signingConfigs { - create("release") { - keyAlias = "key" - - val storePath = - keystoreProperties["path"]?.toString() - ?: System.getenv("KEYSTORE_PATH") - - storeFile = storePath?.let { file(it) } - - keyPassword = - keystoreProperties["password"]?.toString() - ?: System.getenv("KEYSTORE_PASSWORD") - - storePassword = - keystoreProperties["password"]?.toString() - ?: System.getenv("KEYSTORE_PASSWORD") - } - } - - buildTypes { - release { - signingConfig = signingConfigs.getByName("release") - } - - debug { - applicationIdSuffix = ".debug" - } - } -} - -dependencies { - implementation("org.unifiedpush.android:embedded-fcm-distributor:3.1.0") - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} - -kotlin { - compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21 - } -} - -flutter { - source = "../.." -} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..11bc662 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,26 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + + afterEvaluate {project -> + if (project.hasProperty("android")) { + android { + compileSdkVersion 36 + buildToolsVersion '33.0.1' + } + } + } + + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/build.gradle.kts b/android/build.gradle.kts deleted file mode 100644 index 441eb71..0000000 --- a/android/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -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) -} \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5fadfe8..74b269f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..7cffcd0 --- /dev/null +++ b/android/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 "8.13.0" apply false + id "org.jetbrains.kotlin.android" version "2.2.20" apply false +} + +include ":app" diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts deleted file mode 100644 index b59bc94..0000000 --- a/android/settings.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -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 "9.3.1" apply false - id("org.jetbrains.kotlin.android") version "2.4.10" apply false -} - -include(":app") \ No newline at end of file diff --git a/assets/bundled/icon.svg b/assets/icon.svg similarity index 100% rename from assets/bundled/icon.svg rename to assets/icon.svg diff --git a/assets/twim/notif.webp b/assets/twim/notif.webp deleted file mode 100644 index d8d9010..0000000 Binary files a/assets/twim/notif.webp and /dev/null differ diff --git a/assets/twim/oauth.webp b/assets/twim/oauth.webp new file mode 100644 index 0000000..36fbedf Binary files /dev/null and b/assets/twim/oauth.webp differ diff --git a/assets/twim/settings.png b/assets/twim/settings.png new file mode 100644 index 0000000..3037be1 Binary files /dev/null and b/assets/twim/settings.png differ diff --git a/flake.lock b/flake.lock index efbcc03..41cfb9d 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1788450739, - "narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=", + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "type": "github" }, "original": { @@ -42,16 +42,15 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1790043059, - "narHash": "sha256-7ID4HL4IrrlalCsxMASkcAr+v0VHTNFp6+sZ+dpvOV8=", - "owner": "Henry-Hiles", + "lastModified": 1774860670, + "narHash": "sha256-YjJkQrvxrErXtfDi3obUn6rNmkA+CIAZ3f5NgL5xuYE=", + "owner": "neobrain", "repo": "nix2flatpak", - "rev": "290dd282e555cab988c021c2be5f2361e58c4d51", + "rev": "61d68e21e3fbc2d57590051f48736bea271f4aba", "type": "github" }, "original": { - "owner": "Henry-Hiles", - "ref": "quad/nexus", + "owner": "neobrain", "repo": "nix2flatpak", "type": "github" } @@ -74,11 +73,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1788057806, - "narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=", + "lastModified": 1782614948, + "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c", + "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", "type": "github" }, "original": { @@ -89,11 +88,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1790185690, - "narHash": "sha256-xJ+X4hBtOcAFGBOe5nAMyMUeF9foJBmIOu3NjBqBycU=", + "lastModified": 1784497964, + "narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4975466d324710c576dc11ad614684e6bd8cad8e", + "rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 97d6068..2b13a08 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ self.submodules = true; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; - nix2flatpak.url = "github:Henry-Hiles/nix2flatpak/quad/nexus"; + nix2flatpak.url = "github:neobrain/nix2flatpak"; }; outputs = @@ -16,12 +16,62 @@ ... }@inputs: flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ ./linux/nix ]; systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; + + perSystem = + { + lib, + pkgs, + system, + ... + }: + + { + _module.args.pkgs = import nixpkgs { + inherit system; + config = { + android_sdk.accept_license = true; + allowUnfree = true; + }; + }; + + packages = + let + default = pkgs.callPackage ./linux/nix/pkg { + src = self; + }; + in + { + inherit default; + + flatpak = inputs.nix2flatpak.lib.${system}.mkFlatpak { + appName = "Nexus"; + developer = "QuadRadical"; + appId = "nexus.federated.nexus"; + package = default; + runtime = "org.gnome.Platform/49"; + permissions = { + share = [ "network" ]; + sockets = [ + "pulseaudio" + "fallback-x11" + "wayland" + ]; + devices = [ "dri" ]; + }; + }; + + gomuks = pkgs.callPackage ./linux/nix/pkg/gomuks.nix { + src = self; + }; + }; + + devShells.default = pkgs.callPackage ./linux/nix/devshell.nix { }; + }; }; } diff --git a/gomuks b/gomuks index 480938f..2d566a2 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit 480938f9e01e7eae2cf249657efae03df76599d6 +Subproject commit 2d566a2b585e2dba882714f7ca741a52afb36237 diff --git a/hook/build.dart b/hook/build.dart index 9315e04..625201d 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,5 +1,4 @@ import "dart:io"; - import "package:collection/collection.dart"; import "package:hooks/hooks.dart"; import "package:code_assets/code_assets.dart"; @@ -14,46 +13,22 @@ Future main(List args) => build(args, (input, output) async { String libFileName; Map extraEnv = {}; - IOSSdk? iosSdk; - String? iosSdkPath; - String? iosTargetTriple; switch (targetOS) { case OS.linux: libFileName = "libgomuks.so"; break; - case OS.iOS: - // Go only supports buildmode=c-archive for ios, not c-shared, so the - // archive built below gets linked into a dylib via a separate clang - // invocation before being registered as a normal asset like macOS. - libFileName = "libgomuks.dylib"; - final iosConfig = codeConfig.iOS; - iosSdk = iosConfig.targetSdk; - final minVersion = iosConfig.targetVersion; - iosSdkPath = await getXCodeTool(sdkType: iosSdk.type); - - final archTriple = switch (targetArch) { - Architecture.arm64 => "arm64", - Architecture.x64 => "x86_64", - _ => throw UnsupportedError( - "Unsupported iOS architecture: $targetArch", - ), - }; - - iosTargetTriple = iosSdk == IOSSdk.iPhoneSimulator - ? "$archTriple-apple-ios$minVersion.0-simulator" - : "$archTriple-apple-ios$minVersion.0"; - extraEnv = { - "GOOS": "ios", - "CC": await getXCodeTool(sdkType: iosSdk.type, findTool: "clang"), - "CGO_CFLAGS": "-isysroot $iosSdkPath -target $iosTargetTriple", - "CGO_LDFLAGS": "-isysroot $iosSdkPath -target $iosTargetTriple", - }; - break; case OS.macOS: libFileName = "libgomuks.dylib"; extraEnv = { - "SDKROOT": await getXCodeTool(), - "MACOSX_DEPLOYMENT_TARGET": codeConfig.macOS.targetVersion.toString(), + "SDKROOT": await getXCodeSDK(), + "CGO_ENABLED": "1", + "GOARCH": switch (targetArch) { + Architecture.arm64 => "arm64", + Architecture.x64 => "amd64", + _ => throw UnsupportedError( + "Unsupported macOS architecture: $targetArch", + ), + }, }; break; case OS.windows: @@ -121,28 +96,15 @@ Future main(List args) => build(args, (input, output) async { final tags = [ "sqlite_fts5", "goolm", - // goheif/dav1d is not supported on Android or iOS, would need to be fixed upstream - if (targetOS == OS.android || targetOS == OS.iOS) "noheic", + // goheif/dav1d is not supported on Android, would need to be fixed upstream + if (targetOS == OS.android) "noheic", ].join(","); - - final archiveFile = targetOS == OS.iOS - ? buildDir.resolve("${targetArch.name}/libgomuks.a") - : libFile; print( - "Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) to ${archiveFile.path}...", + "Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) to ${libFile.path}...", ); final result = await Process.run( "go", - [ - "build", - "-trimpath", - "-ldflags=-s -w", - "-tags", - tags, - "-o", - archiveFile.path, - "-buildmode=${targetOS == OS.iOS ? "c-archive" : "c-shared"}", - ], + ["build", "-tags", tags, "-o", libFile.path, "-buildmode=c-shared"], workingDirectory: gomuksBuildDir.resolve("pkg/ffi/").toFilePath(), environment: { "CGO_ENABLED": "1", @@ -162,35 +124,6 @@ Future main(List args) => build(args, (input, output) async { "Failed to build Gomuks shared library\n${result.stderr}", ); } - - if (targetOS == OS.iOS) { - print("Linking $archiveFile into $libFile..."); - final linkResult = await Process.run("xcrun", [ - "--sdk", - iosSdk!.type, - "clang", - "-dynamiclib", - "-isysroot", - iosSdkPath!, - "-target", - iosTargetTriple!, - "-install_name", - "@rpath/libgomuks.dylib", - "-framework", - "Security", - "-framework", - "CoreFoundation", - "-framework", - "SystemConfiguration", - "-force_load", - archiveFile.path, - "-o", - libFile.path, - ]); - if (linkResult.exitCode != 0) { - throw Exception("Failed to link Gomuks dylib\n${linkResult.stderr}"); - } - } } final generatedFile = "src/third_party/gomuks.g.dart"; diff --git a/ios/.gitignore b/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 391a902..0000000 --- a/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - - diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile deleted file mode 100644 index 620e46e..0000000 --- a/ios/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '13.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index b29d5cb..0000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PODS: - - Flutter (1.0.0) - - media_kit_libs_ios_video (1.0.4): - - Flutter - - media_kit_video (0.0.1): - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - media_kit_libs_ios_video: - :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" - media_kit_video: - :path: ".symlinks/plugins/media_kit_video/ios" - -SPEC CHECKSUMS: - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 - media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 - -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e - -COCOAPODS: 1.17.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 065d08b..0000000 --- a/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,759 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2ADFEE6C745FFB2100CC9AFD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8156A45F510382D558AED93A /* Pods_RunnerTests.framework */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 88D55A5F1C7625122EBEE29D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82F9A3759658343C81A0EA4C /* Pods_Runner.framework */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 22DE4310DAFBE211CDF700B1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 363A89F6FA09BCCBDED440CE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 63516CF65A79A5DDDE64B0D1 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8156A45F510382D558AED93A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 82F9A3759658343C81A0EA4C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AEB96D8976BD46D6860C7041 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - B8B599FDB72E4713FB7B5A44 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - FE5E4162842555A83F8A7FD7 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 8C0A36760DA8B873D8F6DDCE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2ADFEE6C745FFB2100CC9AFD /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 88D55A5F1C7625122EBEE29D /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 1CFFAE23B502BCCEB2B191A3 /* Pods */ = { - isa = PBXGroup; - children = ( - AEB96D8976BD46D6860C7041 /* Pods-Runner.debug.xcconfig */, - 363A89F6FA09BCCBDED440CE /* Pods-Runner.release.xcconfig */, - 22DE4310DAFBE211CDF700B1 /* Pods-Runner.profile.xcconfig */, - B8B599FDB72E4713FB7B5A44 /* Pods-RunnerTests.debug.xcconfig */, - FE5E4162842555A83F8A7FD7 /* Pods-RunnerTests.release.xcconfig */, - 63516CF65A79A5DDDE64B0D1 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 594EB1F65336CF9C7958F6FA /* Frameworks */ = { - isa = PBXGroup; - children = ( - 82F9A3759658343C81A0EA4C /* Pods_Runner.framework */, - 8156A45F510382D558AED93A /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - 1CFFAE23B502BCCEB2B191A3 /* Pods */, - 594EB1F65336CF9C7958F6FA /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - EE82EC529EB6C0F1FA480659 /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - 8C0A36760DA8B873D8F6DDCE /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 0830C6758DA3ECA81FB12386 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 1D165FDFB9D0D939B041966D /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 0830C6758DA3ECA81FB12386 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 1D165FDFB9D0D939B041966D /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - EE82EC529EB6C0F1FA480659 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = PK66NJM372; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B8B599FDB72E4713FB7B5A44 /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = FE5E4162842555A83F8A7FD7 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 63516CF65A79A5DDDE64B0D1 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = PK66NJM372; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = PK66NJM372; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index c3fedb2..0000000 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc1..0000000 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift deleted file mode 100644 index c73d4ae..0000000 --- a/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { - GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) - } -} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d0d98aa..0000000 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1 +0,0 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index 1682af0..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 51f4a9b..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 97b3b3f..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 17d971e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index ca74554..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 6129702..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 16c3d1e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 97b3b3f..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index f515ebd..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 2e49a13..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png deleted file mode 100644 index 66b9ef2..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png deleted file mode 100644 index 53af9ff..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index 455803d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png deleted file mode 100644 index 7a2fd0f..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 2e49a13..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index c34ad9e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index 5e21545..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png deleted file mode 100644 index 2140e17..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index a27d854..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 8b4eb75..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 21c424b..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist deleted file mode 100644 index 7680b42..0000000 --- a/ios/Runner/Info.plist +++ /dev/null @@ -1,79 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Nexus - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - nexus - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - nexus.federated.nexus - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneClassName - UIWindowScene - UISceneConfigurationName - flutter - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift deleted file mode 100644 index b9ce8ea..0000000 --- a/ios/Runner/SceneDelegate.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Flutter -import UIKit - -class SceneDelegate: FlutterSceneDelegate { - -} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b..0000000 --- a/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/lib/controllers/attachment.dart b/lib/controllers/attachment.dart index d4a5e09..2a99ee5 100644 --- a/lib/controllers/attachment.dart +++ b/lib/controllers/attachment.dart @@ -6,8 +6,10 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/message.dart"; import "package:path/path.dart"; -class AttachmentController(final String roomId) - extends Notifier<(String, MessageContent?)?> { +class AttachmentController extends Notifier<(String, MessageContent?)?> { + final String roomId; + AttachmentController(this.roomId); + @override Null build() => null; diff --git a/lib/controllers/auth_url.dart b/lib/controllers/auth_url.dart index 963df86..156eb81 100644 --- a/lib/controllers/auth_url.dart +++ b/lib/controllers/auth_url.dart @@ -4,8 +4,10 @@ import "package:nexus/controllers/client_id.dart"; import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/requests/oauth/get_auth_url.dart"; -class AuthUrlController(final Uri homeserver) - extends AsyncNotifier { +class AuthUrlController extends AsyncNotifier { + final Uri homeserver; + AuthUrlController(this.homeserver); + @override Future build() async => ref .watch(ClientController.provider.notifier) diff --git a/lib/controllers/author.dart b/lib/controllers/author.dart index f9c9167..bbbe068 100644 --- a/lib/controllers/author.dart +++ b/lib/controllers/author.dart @@ -1,17 +1,19 @@ import "dart:async"; - import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/user.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/event.dart"; -class AuthorController(final Event event) - extends AsyncNotifier { +class AuthorController extends AsyncNotifier { + final Event event; + AuthorController(this.event); + @override Future build() async { final member = await ref.watch( - UserController.provider(.new(roomId: event.roomId, userId: event.sender)) - .future, + UserController.provider( + .new(roomId: event.roomId, userId: event.sender), + ).future, ); return .new( diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 4d0c972..aa8c3ae 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -1,28 +1,26 @@ -import "dart:async"; import "dart:ffi"; import "dart:io"; import "dart:isolate"; import "dart:math"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:ffi/ffi.dart"; -import "package:flutter/cupertino.dart"; -import "package:intl/intl.dart"; +import "package:flutter/foundation.dart"; +import "package:nexus/controllers/account_data.dart"; +import "package:nexus/controllers/client_state.dart"; +import "package:nexus/controllers/init_complete.dart"; +import "package:nexus/controllers/rooms.dart"; +import "package:nexus/controllers/space_edges.dart"; +import "package:nexus/controllers/sync_status.dart"; +import "package:nexus/controllers/top_level_spaces.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart"; -import "package:nexus/models/capabilities.dart"; +import "package:nexus/main.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; -import "package:nexus/models/event_context.dart"; -import "package:nexus/models/gomuks_config.dart"; import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/paginate.dart"; -import "package:nexus/models/paginate_manual.dart"; -import "package:nexus/models/requests/deregister_pusher.dart"; import "package:nexus/models/requests/download_media.dart"; import "package:nexus/models/requests/get_event.dart"; -import "package:nexus/models/requests/get_event_context.dart"; -import "package:nexus/models/requests/get_mentions.dart"; import "package:nexus/models/requests/get_related_events.dart"; import "package:nexus/models/requests/get_room_state.dart"; import "package:nexus/models/requests/join_room.dart"; @@ -31,9 +29,7 @@ import "package:nexus/models/requests/oauth/exchange_token.dart"; import "package:nexus/models/requests/oauth/get_auth_url.dart"; import "package:nexus/models/requests/oauth/register_client.dart"; import "package:nexus/models/requests/paginate.dart"; -import "package:nexus/models/requests/paginate_manual.dart"; import "package:nexus/models/requests/redact_event.dart"; -import "package:nexus/models/requests/register_pusher.dart"; import "package:nexus/models/requests/report.dart"; import "package:nexus/models/requests/send_event.dart"; import "package:nexus/models/requests/send_message.dart"; @@ -42,9 +38,8 @@ import "package:nexus/models/requests/set_membership.dart"; import "package:nexus/models/requests/set_state.dart"; import "package:nexus/models/requests/upload_media.dart"; import "package:nexus/models/room.dart"; -import "package:nexus/models/room_metadata.dart"; -import "package:nexus/models/room_summary.dart"; import "package:nexus/models/spec_versions_response.dart"; +import "package:nexus/models/sync_data.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:path_provider/path_provider.dart"; @@ -52,99 +47,134 @@ import "package:path_provider/path_provider.dart"; class ClientController extends AsyncNotifier { @override Future build() async { - debugPrint("Setting Gomuks env..."); - if (Platform.isAndroid || Platform.isIOS) { - final env = { - "GOMUKS_ROOT": (await getApplicationSupportDirectory()).path, - "GOMUKS_CACHE_HOME": (await getApplicationCacheDirectory()).path, - }; - for (final MapEntry(:key, :value) in env.entries) { - final keyPtr = key.toNativeUtf8().cast(); - final valuePtr = value.toNativeUtf8().cast(); - try { - GomuksSetEnv(keyPtr, valuePtr); - } finally { - calloc - ..free(keyPtr) - ..free(valuePtr); - } - } + final Pointer root; + if (Platform.isAndroid) { + final dir = await getApplicationSupportDirectory(); + root = "${dir.path}/gomuks".toNativeUtf8().cast(); + } else { + root = nullptr.cast(); } - debugPrint("Initializing Gomuks..."); - final handle = await Isolate.run(() { - final bufferPointer = GomuksConfig( - matrix: .new( - initialDeviceDisplayName: - "Nexus on ${toBeginningOfSentenceCase(Platform.operatingSystem)}", - ), - ).toJson().toGomuksBufferPtr(); + final handle = GomuksInit(root); - try { - return GomuksInit(bufferPointer.ref); - } finally { - calloc - ..free(bufferPointer.ref.base) - ..free(bufferPointer); - } - }); + final callable = + NativeCallable< + Void Function(Pointer, Int64, GomuksOwnedBuffer) + >.listener(( + Pointer command, + int requestId, + GomuksOwnedBuffer data, + ) { + try { + final muksEventType = command.cast().toDartString(); + debugPrint("Handling $muksEventType..."); + final decodedMuksEvent = data.toJson(); + + switch (muksEventType) { + case "client_state": + ref + .watch(ClientStateController.provider.notifier) + .set(.fromJson(decodedMuksEvent)); + break; + case "sync_status": + ref + .watch(SyncStatusController.provider.notifier) + .set(.fromJson(decodedMuksEvent)); + break; + case "init_complete": + ref.watch(InitCompleteController.provider.notifier).complete(); + break; + case "send_complete": + final event = Event.fromJson(decodedMuksEvent["event"]); + ref + .watch(RoomsController.provider.notifier) + .update( + .new({ + event.roomId: .new(events: .new({event.rowId: event})), + }), + .new(), + ); + + break; + case "sync_complete": + final syncData = SyncData.fromJson(decodedMuksEvent); + final roomProvider = RoomsController.provider; + final accountDataProvider = AccountDataController.provider; + + if (syncData.clearState) { + ref.invalidate(roomProvider); + ref.invalidate(accountDataProvider); + } + + ref + .watch(roomProvider.notifier) + .update(syncData.rooms, syncData.leftRooms); + ref + .watch(accountDataProvider.notifier) + .update(syncData.accountData); + + if (syncData.topLevelSpaces != null) { + ref + .watch(TopLevelSpacesController.provider.notifier) + .set(syncData.topLevelSpaces!); + } + + if (syncData.spaceEdges != null) { + ref + .watch(SpaceEdgesController.provider.notifier) + .set(syncData.spaceEdges!); + } + + // ref + // .watch(SyncStatusController.provider.notifier) + // .set(SyncStatus.fromJson(decodedMuksEvent)); + break; + default: + debugPrint("Unhandled event: $muksEventType"); + } + debugPrint("Finished handling $muksEventType..."); + } catch (error, stackTrace) { + if (kDebugMode) { + debugPrintStack(stackTrace: stackTrace, label: error.toString()); + rethrow; + } else { + showError(error, stackTrace); + } + } + }); ref.onDispose(() => GomuksDestroy(handle)); + ref.onDispose(callable.close); - return handle; + final errorCode = GomuksStart(handle, callable.nativeFunction); + + if (errorCode == 0) return handle; + throw Exception("GomuksStart returned error code $errorCode"); } - Future callGomuksMethod( - Map data, - FutureOr Function(int handle, GomuksBorrowedBuffer data) - callback, - ) async { - final bufferPointer = data.toGomuksBufferPtr(); - - try { - final handle = await future; - - final response = await Isolate.run( - () => callback(handle, bufferPointer.ref), - ); - - final json = response.buf.toJson(); - - if (response.command.cast().toDartString() == "error") { - throw json; - } - - return json; - } finally { - calloc - ..free(bufferPointer.ref.base) - ..free(bufferPointer); - } - } - - Future<(Event, RoomMetadata)> handlePush(Map data) async { - final response = await callGomuksMethod( - data, - (handle, data) async => GomuksHandlePush(handle, data), - ); - - return ( - Event.fromJson(response["event"]), - RoomMetadata.fromJson(response["room"]), - ); - } - - dynamic _sendCommand( + Future _sendCommand( String command, [ Map data = const {}, - ]) => callGomuksMethod(data, (handle, data) { - final commandPointer = command.toNativeUtf8().cast(); - try { - return GomuksSubmitCommand(handle, commandPointer, data); - } finally { - calloc.free(commandPointer); + ]) async { + final bufferPointer = data.toGomuksBufferPtr(); + final handle = await future; + final response = await Isolate.run( + () => GomuksSubmitCommand( + handle, + command.toNativeUtf8().cast(), + bufferPointer.ref, + ), + ); + + calloc.free(bufferPointer); + + final json = response.buf.toJson(); + if (response.command.cast().toDartString() == "error") { + throw json; } - }); + + return json; + } Future redactEvent(RedactEventRequest report) => _sendCommand("redact_event", report.toJson()); @@ -183,17 +213,21 @@ class ClientController extends AsyncNotifier { } } - Future joinRoom(JoinRoomRequest request) async => - (await _sendCommand("join_room", request.toJson()))["room_id"]; - - Future getRoomSummary(JoinRoomRequest request) async => - .fromJson(await _sendCommand("get_room_summary", request.toJson())); + Future joinRoom(JoinRoomRequest request) async { + final response = await _sendCommand("join_room", request.toJson()); + return response["room_id"]; + } Future leaveRoom(Room room) async { if (room.metadata == null) return; await _sendCommand("leave_room", {"room_id": room.metadata!.id}); } + // (await _sendCommand("get_event_context", { + // "room_id": request.roomId, + // "event_id": r"$OqZT4NuTj0J1-771IOEEWRI4XdumRNu6ighlvO3K3gc", + // })); + Future> getRoomState(GetRoomStateRequest request) async { Future getState(GetRoomStateRequest request) async => (await _sendCommand("get_room_state", request.toJson())) as List?; @@ -214,12 +248,6 @@ class ClientController extends AsyncNotifier { return .new(response?.map((event) => .fromJson(event))); } - Future> getMentions(GetMentionsRequest request) async => .new( - // TODO: Handle `related_events` - ((await _sendCommand("get_mentions", request.toJson()))["events"] as List) - .map((event) => .fromJson(event)), - ); - Future getEvent(GetEventRequest request) async { final json = await _sendCommand("get_event", request.toJson()); return json == null ? null : .fromJson(json); @@ -231,19 +259,8 @@ class ClientController extends AsyncNotifier { Future paginate(PaginateRequest request) async => .fromJson(await _sendCommand("paginate", request.toJson())); - Future paginateManual(PaginateManualRequest request) async => - .fromJson(await _sendCommand("paginate_manual", request.toJson())); - - Future getEventContext(GetEventContextRequest request) async => - .fromJson(await _sendCommand("get_event_context", request.toJson())); - - Future getProfile(String userId) async { - try { - return .fromJson(await _sendCommand("get_profile", {"user_id": userId})); - } catch (_) { - return ProfileResponse(profile: .new(id: userId)); - } - } + Future getProfile(String userId) async => + .fromJson(await _sendCommand("get_profile", {"user_id": userId})); Future reportEvent(ReportRequest request) => _sendCommand("report_event", request.toJson()); @@ -254,15 +271,6 @@ class ClientController extends AsyncNotifier { Future setAccountData(SetAccountDataRequest request) => _sendCommand("set_account_data", request.toJson()); - Future registerPusher(RegisterPusherRequest request) => - _sendCommand("register_homeserver_push", request.toJson()); - - Future deregisterPusher(DeregisterPusherRequest request) => - _sendCommand("register_homeserver_push", { - ...request.toJson(), - "kind": null, - }); - Future uploadMedia(UploadMediaRequest request) async => .fromJson(await _sendCommand("upload_media", request.toJson())); @@ -272,11 +280,9 @@ class ClientController extends AsyncNotifier { Future logout() => _sendCommand("logout"); Future markRead(Room room) async { - if (room.timeline.isEmpty || room.metadata == null) return; final eventRowId = room.timeline[room.timeline.keys.reduce(max)]; final event = eventRowId == null ? null : room.events[eventRowId]; - - if (event == null) return; + if (event == null || room.metadata == null) return; await _sendCommand("mark_read", { "room_id": room.metadata!.id, @@ -302,14 +308,10 @@ class ClientController extends AsyncNotifier { Future getSpecVersions() async => .fromJson(await _sendCommand("get_versions")); - Future getCapabilities() async => Capabilities.fromJson( - (await _sendCommand("get_capabilities"))["capabilities"], - ); - Future discoverHomeserver(Uri homeserver) async { try { final response = await _sendCommand("discover_homeserver", { - "user_id": "@fake-user:${homeserver.authority}", + "user_id": "@fake-user:${homeserver.host}", }); return Uri.parse(response["m.homeserver"]?["base_url"]); } catch (error) { diff --git a/lib/controllers/client_id.dart b/lib/controllers/client_id.dart index 7748f97..ac8f5cb 100644 --- a/lib/controllers/client_id.dart +++ b/lib/controllers/client_id.dart @@ -1,7 +1,10 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; -class ClientIdController(final Uri homeserver) extends AsyncNotifier { +class ClientIdController extends AsyncNotifier { + final Uri homeserver; + ClientIdController(this.homeserver); + @override Future build() => ref .watch(ClientController.provider.notifier) diff --git a/lib/controllers/emoji.dart b/lib/controllers/emoji.dart new file mode 100644 index 0000000..caea3de --- /dev/null +++ b/lib/controllers/emoji.dart @@ -0,0 +1,84 @@ +import "dart:convert"; +import "package:emoji_text_field/models/emoji_category.dart"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:http/http.dart"; +import "package:nexus/models/emoji.dart"; + +typedef EmojiTuple = (IMap, IMap>); + +class EmojiController extends AsyncNotifier { + @override + Future build() async { + final response = await get( + .https("github.com", "github/gemoji/raw/refs/heads/master/db/emoji.json"), + ); + + if (response.statusCode != 200) { + throw Exception("Failed to load emoji data"); + } + + final data = json.decode(response.body); + + final entries = (data as List) + .cast>() + .map(Emoji.fromJson) + .toIList(); + + final categoryMap = entries.fold>>( + .new(), + (acc, entry) => acc.update( + entry.category, + (list) => list.add(entry.emoji), + ifAbsent: () => .new([entry.emoji]), + ), + ); + + final keywordMap = entries.fold>>( + .new(), + (acc, entry) => acc.add( + entry.emoji, + .new([...entry.tags, ...entry.aliases, entry.description]), + ), + ); + + final customCategories = IMap.fromEntries( + categoryMap.entries.map( + (entry) => MapEntry( + entry.key, + EmojiCategory( + name: entry.key, + icon: switch (entry.key) { + "Smileys & Emotion" => Icons.emoji_emotions, + "People & Body" => Icons.emoji_people, + "Animals & Nature" => Icons.emoji_nature, + "Food & Drink" => Icons.emoji_food_beverage, + "Travel & Places" => Icons.travel_explore, + "Activities" => Icons.sports_soccer, + "Objects" => Icons.emoji_objects, + "Symbols" => Icons.emoji_symbols, + "Flags" => Icons.emoji_flags, + _ => Icons.category, + }, + emojis: entry.value.toList(growable: false), + ), + ), + ), + ); + + final customKeywords = IMap( + .fromEntries( + keywordMap.entries.map( + (e) => .new(e.key, e.value.toList(growable: false)), + ), + ), + ); + + return (customCategories, customKeywords); + } + + static final provider = AsyncNotifierProvider( + EmojiController.new, + ); +} diff --git a/lib/controllers/event.dart b/lib/controllers/event.dart index 7a1e5dc..b7b417a 100644 --- a/lib/controllers/event.dart +++ b/lib/controllers/event.dart @@ -5,8 +5,10 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/get_event.dart"; -class EventController(final GetEventRequest request) - extends AsyncNotifier { +class EventController extends AsyncNotifier { + final GetEventRequest request; + EventController(this.request); + @override Future build() async { final room = ref.watch( diff --git a/lib/controllers/gomuks_listener.dart b/lib/controllers/gomuks_listener.dart deleted file mode 100644 index dd75cf3..0000000 --- a/lib/controllers/gomuks_listener.dart +++ /dev/null @@ -1,124 +0,0 @@ -import "dart:async"; -import "dart:ffi"; - -import "package:flutter/foundation.dart"; -import "package:nexus/controllers/account_data.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/init_complete.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/controllers/space_edges.dart"; -import "package:nexus/controllers/sync_status.dart"; -import "package:nexus/controllers/top_level_spaces.dart"; -import "package:nexus/helpers/extensions/gomuks_buffer.dart"; -import "package:nexus/main.dart"; -import "package:ffi/ffi.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/models/sync_data.dart"; -import "package:nexus/src/third_party/gomuks.g.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; - -class GomuksListenerController extends AsyncNotifier { - @override - Future build() async { - debugPrint("Starting gomuks..."); - final handle = await ref.watch(ClientController.provider.future); - - final callable = - NativeCallable< - Void Function(Pointer, Int64, GomuksOwnedBuffer) - >.listener(( - Pointer command, - int requestId, - GomuksOwnedBuffer data, - ) { - try { - final muksEventType = command.cast().toDartString(); - debugPrint("Handling $muksEventType..."); - final decodedMuksEvent = data.toJson(); - - switch (muksEventType) { - case "client_state": - ref - .watch(ClientStateController.provider.notifier) - .set(.fromJson(decodedMuksEvent)); - break; - case "sync_status": - ref - .watch(SyncStatusController.provider.notifier) - .set(.fromJson(decodedMuksEvent)); - break; - case "init_complete": - ref.watch(InitCompleteController.provider.notifier).complete(); - break; - case "send_complete": - final event = Event.fromJson(decodedMuksEvent["event"]); - ref - .watch(RoomsController.provider.notifier) - .update( - .new({ - event.roomId: .new(events: .new({event.rowId: event})), - }), - .new(), - ); - - break; - case "sync_complete": - final syncData = SyncData.fromJson(decodedMuksEvent); - final roomProvider = RoomsController.provider; - final accountDataProvider = AccountDataController.provider; - - if (syncData.clearState) { - ref.invalidate(roomProvider); - ref.invalidate(accountDataProvider); - } - - ref - .watch(roomProvider.notifier) - .update(syncData.rooms, syncData.leftRooms); - ref - .watch(accountDataProvider.notifier) - .update(syncData.accountData); - - if (syncData.topLevelSpaces != null) { - ref - .watch(TopLevelSpacesController.provider.notifier) - .set(syncData.topLevelSpaces!); - } - - if (syncData.spaceEdges != null) { - ref - .watch(SpaceEdgesController.provider.notifier) - .set(syncData.spaceEdges!); - } - - // ref - // .watch(SyncStatusController.provider.notifier) - // .set(SyncStatus.fromJson(decodedMuksEvent)); - break; - default: - debugPrint("Unhandled event: $muksEventType"); - } - debugPrint("Finished handling $muksEventType..."); - } catch (error, stackTrace) { - if (kDebugMode) { - debugPrintStack(stackTrace: stackTrace, label: error.toString()); - rethrow; - } else { - showError(error, stackTrace); - } - } - }); - - ref.onDispose(callable.close); - - final errorCode = GomuksStart(handle, callable.nativeFunction); - if (errorCode != 0) { - throw Exception("GomuksStart returned error code $errorCode"); - } - } - - static final provider = AsyncNotifierProvider( - GomuksListenerController.new, - ); -} diff --git a/lib/controllers/jump_to_event.dart b/lib/controllers/jump_to_event.dart deleted file mode 100644 index da42cf9..0000000 --- a/lib/controllers/jump_to_event.dart +++ /dev/null @@ -1,16 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; - -class JumpToEventController(String? _) extends Notifier { - @override - String? build() => null; - - void set(String? eventId) => state = eventId; - - @override - bool updateShouldNotify(_, _) => true; - - static final provider = NotifierProvider.family - .autoDispose( - JumpToEventController.new, - ); -} diff --git a/lib/controllers/key.dart b/lib/controllers/key.dart index 77b4883..eff3ab2 100644 --- a/lib/controllers/key.dart +++ b/lib/controllers/key.dart @@ -1,28 +1,30 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/shared_prefs.dart"; -class KeyController(final String key) extends AsyncNotifier { +class KeyController extends Notifier { + final String key; + KeyController(this.key); + static const String spaceKey = "space"; static const String roomKey = "room"; - static const String pushKeyKey = "pushKey"; @override - Future build() => - ref.watch(SharedPrefsController.provider).getString(key); + String? build() => + ref.watch(SharedPrefsController.provider).requireValue.getString(key); Future set(String? value) async { - final prefs = ref.watch(SharedPrefsController.provider); - state = .data(value); + final prefs = ref.watch(SharedPrefsController.provider).requireValue; + state = value; if (value == null) { - await prefs.remove(key); + prefs.remove(key); } else { - await prefs.setString(key, value); + prefs.setString(key, value); } } static final provider = - AsyncNotifierProvider.family( + NotifierProvider.family( KeyController.new, ); } diff --git a/lib/controllers/member_list_opened.dart b/lib/controllers/member_list_opened.dart index f0295d8..e3509f0 100644 --- a/lib/controllers/member_list_opened.dart +++ b/lib/controllers/member_list_opened.dart @@ -1,20 +1,22 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/shared_prefs.dart"; -class MemberListOpenedController extends AsyncNotifier { +class MemberListOpenedController extends Notifier { static const String key = "memberListOpened"; @override - Future build() async => - await ref.watch(SharedPrefsController.provider).getBool(key) ?? true; + bool build() => + ref.watch(SharedPrefsController.provider).requireValue.getBool(key) ?? + true; Future set(bool value) async { - state = .data(value); - await ref.watch(SharedPrefsController.provider).setBool(key, value); + final prefs = ref.watch(SharedPrefsController.provider).requireValue; + state = value; + + prefs.setBool(key, value); } - static final provider = - AsyncNotifierProvider( - MemberListOpenedController.new, - ); + static final provider = NotifierProvider( + MemberListOpenedController.new, + ); } diff --git a/lib/controllers/members.dart b/lib/controllers/members.dart index 3c287ea..8566b40 100644 --- a/lib/controllers/members.dart +++ b/lib/controllers/members.dart @@ -6,8 +6,10 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/get_room_state.dart"; -class MembersController(final String roomId) - extends AsyncNotifier> { +class MembersController extends AsyncNotifier> { + final String roomId; + MembersController(this.roomId); + @override Future> build() async { final room = ref.watch( diff --git a/lib/controllers/members_by_status.dart b/lib/controllers/members_by_status.dart index 82905aa..613f7b1 100644 --- a/lib/controllers/members_by_status.dart +++ b/lib/controllers/members_by_status.dart @@ -5,8 +5,10 @@ import "package:nexus/models/configs/members_by_status.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/event.dart"; -class MembersByStatusController(final MembersByStatusConfig config) - extends AsyncNotifier> { +class MembersByStatusController extends AsyncNotifier> { + final MembersByStatusConfig config; + MembersByStatusController(this.config); + @override Future> build() => ref.watch( MembersController.provider(config.roomId).selectAsync( diff --git a/lib/controllers/members_grouped.dart b/lib/controllers/members_grouped.dart index f903ec4..6f41e9d 100644 --- a/lib/controllers/members_grouped.dart +++ b/lib/controllers/members_grouped.dart @@ -8,8 +8,11 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/event.dart"; -class MembersGroupedController(final MembersByStatusConfig config) +class MembersGroupedController extends AsyncNotifier>>> { + final MembersByStatusConfig config; + MembersGroupedController(this.config); + @override Future>>> build() async { final room = ref.watch( diff --git a/lib/controllers/multi_provider.dart b/lib/controllers/multi_provider.dart index ab37d07..52dd8d9 100644 --- a/lib/controllers/multi_provider.dart +++ b/lib/controllers/multi_provider.dart @@ -1,15 +1,14 @@ import "dart:async"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -class MultiProviderController(final IList providers) - extends AsyncNotifier { +class MultiProviderController extends AsyncNotifier { + MultiProviderController(this.providers); + final IList providers; + @override - Future build() => .wait( - providers.map((provider) => ref.watch(provider.future)), - eagerError: true, - ); + Future build() => + .wait(providers.map((provider) => ref.watch(provider.future))); static final provider = AsyncNotifierProvider.family< diff --git a/lib/controllers/notification.dart b/lib/controllers/notification.dart deleted file mode 100644 index 5ee3449..0000000 --- a/lib/controllers/notification.dart +++ /dev/null @@ -1,162 +0,0 @@ -import "dart:async"; -import "dart:io"; - -import "package:flutter/services.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/controllers/portal.dart"; -import "package:nexus/main.dart"; -import "package:flutter_local_notifications/flutter_local_notifications.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/widgets/pages/notifications.dart"; -import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; - -class NotificationController - extends AsyncNotifier { - @override - Future build() async { - final notifications = FlutterLocalNotificationsPlugin(); - - if (!Platform.isLinux) { - final darwin = DarwinInitializationSettings(); - - await notifications.initialize( - settings: .new( - windows: .new( - appName: "Nexus", - appUserModelId: "nexus.federated.nexus", - guid: "dde78daf-130f-4e46-a80a-e31deeab45d7", - ), - android: .new("ic_launcher_foreground"), - iOS: darwin, - macOS: darwin, - ), - onDidReceiveNotificationResponse: (details) { - if (details.payload case final eventId?) { - if (navigatorKey.currentContext case final context?) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => NotificationsPage( - highlightedEventId: eventId, - defaultToAllNotifications: true, - ), - ), - ); - } - } - }, - ); - } - - if (Platform.isLinux) { - const notificationChannel = MethodChannel("nexus/notifications"); - - notificationChannel.setMethodCallHandler((call) async { - if (call.method != "notificationClicked") { - return; - } - - final eventId = call.arguments as String; - - if (navigatorKey.currentContext case final context?) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => NotificationsPage( - highlightedEventId: eventId, - defaultToAllNotifications: true, - ), - ), - ); - } - }); - - ref.onDispose(() => notificationChannel.setMethodCallHandler(null)); - } - - return notifications; - } - - Future requestPermissions() async { - if (Platform.isLinux) { - return true; - } - - final controller = await future; - - if (Platform.isIOS) { - return await controller - .resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin - >() - ?.requestPermissions(alert: true, badge: true, sound: true) ?? - true; - } else if (Platform.isMacOS) { - return await controller - .resolvePlatformSpecificImplementation< - MacOSFlutterLocalNotificationsPlugin - >() - ?.requestPermissions(alert: true, badge: true, sound: true) ?? - true; - } else if (Platform.isAndroid) { - return await controller - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin - >() - ?.requestNotificationsPermission() ?? - true; - } - - return true; - } - - Future send({ - required int id, - required String title, - String? body, - File? icon, - String? payload, - }) async { - debugPrint("Sending notification for $id"); - - if (Platform.isLinux) { - final portal = await ref.watch(PortalController.provider.future); - - await portal.notification.addNotification( - id.toString(), - title: title, - body: body, - icon: icon == null ? null : XdgNotificationIconFile(icon.path), - defaultAction: "app.event", - defaultActionTarget: payload, - ); - } else { - final notificationDetails = NotificationDetails( - android: .new( - "messages", - "Messages", - largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path), - ), - // TODO: See if icons can be added to iOS, macOS, and Windows - // notifications (#68) - iOS: .new(), - macOS: .new(), - windows: .new(), - ); - - final controller = await future; - - await controller.show( - id: id, - title: title, - body: body, - notificationDetails: notificationDetails, - payload: payload, - ); - } - } - - static final provider = - AsyncNotifierProvider< - NotificationController, - FlutterLocalNotificationsPlugin - >(NotificationController.new); -} diff --git a/lib/controllers/notifications.dart b/lib/controllers/notifications.dart deleted file mode 100644 index da4312d..0000000 --- a/lib/controllers/notifications.dart +++ /dev/null @@ -1,53 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/models/event.dart"; - -typedef NotificationsRequest = (UnreadType? unreadType, String? roomId); - -class NotificationsController([final NotificationsRequest? request]) - extends AsyncNotifier> { - @override - Future> build() async { - final client = ref.read(ClientController.provider.notifier); - - final (unreadType, roomId) = request ?? (null, null); - - return await client.getMentions( - .new( - maxTimestamp: .now(), - unreadType: unreadType ?? .highlight, - roomId: roomId, - ), - ); - } - - Future loadOlder() async { - final currentNotifications = await future; - state = .loading(); - state = await .guard(() async { - final lastTs = currentNotifications.lastOrNull?.timestamp; - if (lastTs == null) return const .empty(); - - final client = ref.read(ClientController.provider.notifier); - final (unreadType, roomId) = request ?? (null, null); - - final newNotifications = await client.getMentions( - .new( - maxTimestamp: lastTs, - unreadType: unreadType ?? .highlight, - roomId: roomId, - ), - ); - - return currentNotifications.addAll(newNotifications); - }); - } - - static final provider = AsyncNotifierProvider.family - .autoDispose< - NotificationsController, - IList, - NotificationsRequest? - >(NotificationsController.new); -} diff --git a/lib/controllers/pinned_events.dart b/lib/controllers/pinned_events.dart index 323a275..914a301 100644 --- a/lib/controllers/pinned_events.dart +++ b/lib/controllers/pinned_events.dart @@ -4,8 +4,10 @@ import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/pinned_ids.dart"; import "package:nexus/models/event.dart"; -class PinnedEventsController(final String roomId) - extends AsyncNotifier> { +class PinnedEventsController extends AsyncNotifier> { + final String roomId; + PinnedEventsController(this.roomId); + @override Future> build() async { final pinIds = ref.watch(PinnedIdsController.provider(roomId)); @@ -13,8 +15,9 @@ class PinnedEventsController(final String roomId) return (await Future.wait( pinIds.map( (eventId) => ref.watch( - EventController.provider(.new(eventId: eventId, roomId: roomId)) - .future, + EventController.provider( + .new(eventId: eventId, roomId: roomId), + ).future, ), ), )).nonNulls.toIList(); diff --git a/lib/controllers/pinned_ids.dart b/lib/controllers/pinned_ids.dart index 01358a9..3d1e5da 100644 --- a/lib/controllers/pinned_ids.dart +++ b/lib/controllers/pinned_ids.dart @@ -5,7 +5,10 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/pinned_events.dart"; -class PinnedIdsController(final String roomId) extends Notifier> { +class PinnedIdsController extends Notifier> { + final String roomId; + PinnedIdsController(this.roomId); + @override IList build() { final room = ref.watch( diff --git a/lib/controllers/portal.dart b/lib/controllers/portal.dart deleted file mode 100644 index daae24a..0000000 --- a/lib/controllers/portal.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "dart:io"; - -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; - -class PortalController extends AsyncNotifier { - @override - Future build() async { - final portal = XdgDesktopPortalClient(); - if (!await File("/.flatpak-info").exists()) { - await portal.registerApplication("nexus.federated.nexus"); - } - - ref.onDispose(portal.close); - return portal; - } - - static final provider = - AsyncNotifierProvider( - PortalController.new, - ); -} diff --git a/lib/controllers/power_level.dart b/lib/controllers/power_level.dart index 7111e38..2f0c72e 100644 --- a/lib/controllers/power_level.dart +++ b/lib/controllers/power_level.dart @@ -6,8 +6,10 @@ import "package:nexus/models/configs/power_level.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/power_levels.dart"; -class PowerLevelController(final PowerLevelConfig config) - extends Notifier { +class PowerLevelController extends Notifier { + final PowerLevelConfig config; + PowerLevelController(this.config); + @override bool build() { if (config case EventPowerLevelConfig(:final eventType)) { diff --git a/lib/controllers/profile.dart b/lib/controllers/profile.dart index 7a1477c..58fa49d 100644 --- a/lib/controllers/profile.dart +++ b/lib/controllers/profile.dart @@ -2,11 +2,13 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/profile_response.dart"; -class ProfileController(final String userId) - extends AsyncNotifier { +class ProfileController extends AsyncNotifier { + final String userId; + ProfileController(this.userId); + @override Future build() { - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); return client.getProfile(userId); } diff --git a/lib/controllers/push_key.dart b/lib/controllers/push_key.dart deleted file mode 100644 index e3f6b82..0000000 --- a/lib/controllers/push_key.dart +++ /dev/null @@ -1,31 +0,0 @@ -import "dart:convert"; - -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/key.dart"; - -class PushKeyController(final String instance) extends AsyncNotifier { - @override - Future build() async => json.decode( - await ref.watch(KeyController.provider(KeyController.pushKeyKey).future) ?? - "{}", - )[instance]; - - Future set(String? value) async { - final provider = KeyController.provider(KeyController.pushKeyKey); - final notifier = ref.watch(provider.notifier); - final prefs = IMap(json.decode(await ref.watch(provider.future) ?? "{}")); - - state = .data(value); - notifier.set( - json.encode( - prefs.add(instance, value).where((_, value) => value != null).unlock, - ), - ); - } - - static final provider = - AsyncNotifierProvider.family( - PushKeyController.new, - ); -} diff --git a/lib/controllers/reactions.dart b/lib/controllers/reactions.dart index 9a92843..615a59d 100644 --- a/lib/controllers/reactions.dart +++ b/lib/controllers/reactions.dart @@ -5,8 +5,10 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/configs/reactions.dart"; import "package:nexus/models/content/reaction.dart"; -class ReactionsController(final ReactionsConfig config) - extends AsyncNotifier>> { +class ReactionsController extends AsyncNotifier>> { + final ReactionsConfig config; + ReactionsController(this.config); + @override Future>> build() async { final eventInfo = ref.watch( diff --git a/lib/controllers/recent_emoji.dart b/lib/controllers/recent_emoji.dart deleted file mode 100644 index 6cf57f7..0000000 --- a/lib/controllers/recent_emoji.dart +++ /dev/null @@ -1,44 +0,0 @@ -import "package:collection/collection.dart"; -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/account_data.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/models/account_data.dart"; - -class RecentEmojiController extends Notifier> { - @override - IList build() => ref.watch( - AccountDataController.provider.select( - (value) => value.recentEmoji.recentEmoji, - ), - ); - - Future add(String emoji) => ref - .watch(ClientController.provider.notifier) - .setAccountData( - .new( - type: AccountData.recentEmojiKey, - content: RecentEmojiData( - recentEmoji: .new([ - .new( - emoji: emoji, - total: - (state - .firstWhereOrNull( - (element) => element.emoji == emoji, - ) - ?.total ?? - 0) + - 1, - ), - ...state.whereNot((element) => element.emoji == emoji), - ]), - ), - ), - ); - - static final provider = - NotifierProvider.autoDispose>( - RecentEmojiController.new, - ); -} diff --git a/lib/controllers/room_chat.dart b/lib/controllers/room_chat.dart index 91b137a..3e531bb 100644 --- a/lib/controllers/room_chat.dart +++ b/lib/controllers/room_chat.dart @@ -1,6 +1,5 @@ import "dart:async"; import "dart:math"; - import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; @@ -10,21 +9,20 @@ import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/reaction.dart"; -import "package:nexus/models/direction.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/redact_event.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/models/requests/send_message.dart"; import "package:nexus/models/room.dart"; -import "package:nexus/models/room_chat.dart"; -class RoomChatController(final (String roomId, String? contextualEvent) info) - extends AsyncNotifier { +class RoomChatController extends AsyncNotifier?> { + final String roomId; + RoomChatController(this.roomId); + @override - Future build() async { - final (roomId, eventId) = info; - final client = ref.read(ClientController.provider.notifier); - final room = ref.read( + Future?> build() async { + final client = ref.watch(ClientController.provider.notifier); + final room = ref.watch( RoomsController.provider.select((rooms) => rooms[roomId]), ); @@ -32,14 +30,21 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) if (!room.hasFetchedState) { final state = await client.getRoomState(.new(roomId: roomId)); + await ref.read(RoomsController.provider.notifier).addState(roomId, state); } - final timeline = room.timeline + // While there are under 20 events, try to load more + // until there's no more or the conditions are met. + if (room.hasMore && room.timeline.length < 20) { + loadOlder(); + } + + return room.timeline .toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0)) .map((element) => element.value) .toIList() - .addAll(room.clientSticky) + .addAll(room.sticky) .map((entry) { final foundEvent = entry == null ? null : room.events[entry]; @@ -57,26 +62,6 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) }) .nonNulls .toIList(); - - if (info.$2 == null || timeline.map((e) => e.eventId).contains(info.$2)) { - ref.watch(RoomsController.provider.select((rooms) => rooms[roomId])); - - return .new( - timeline: timeline, - hasMoreBackward: room.hasMore, - hasMoreForward: false, - ); - } else { - final context = await client.getEventContext( - .new(roomId: roomId, eventId: info.$2!), - ); - return .new( - timeline: context.before.add(context.event).addAll(context.after), - hasMoreBackward: true, - hasMoreForward: true, - historicalData: .new(start: context.start, end: context.end), - ); - } } Future deleteMessage(Event event, {String? reason}) => ref @@ -84,101 +69,49 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) .redactEvent( RedactEventRequest( eventId: event.eventId, - roomId: info.$1, + roomId: roomId, reason: reason, ), ); - Future paginate(Direction direction) async { - if (state.isLoading) return; - - final chat = await future; - - if (direction == .forward - ? chat?.hasMoreForward == false - : chat?.hasMoreBackward == false) { - return; - } - - state = .loading(); - - final client = ref.read(ClientController.provider.notifier); - - if (chat?.historicalData == null) { - final timelineKeys = ref - .read(RoomsController.provider.select((value) => value[info.$1])) - ?.timeline - .keys; - final response = await client.paginate( - .new( - roomId: info.$1, - maxTimelineId: timelineKeys?.isNotEmpty == true - ? timelineKeys?.reduce(min) - : null, - ), - ); - - if (response.events.isEmpty) { - state = .data(state.value); - } - - ref - .read(RoomsController.provider.notifier) - .update( - IMap({ - info.$1: Room( - events: IMap.fromIterable( - response.events.addAll(response.relatedEvents), - keyMapper: (event) => event.rowId, - valueMapper: (event) => event, - ), - hasMore: response.hasMore, - timeline: IMap.fromIterable( - response.events, - keyMapper: (event) => event.timelineRowId, - valueMapper: (event) => event.rowId, - ), - ), - }), - .new(), - ); - } else { - final paginationResponse = await client.paginateManual( - .new( - roomId: info.$1, - direction: direction, - since: direction == .forward - ? chat!.historicalData!.end - : chat!.historicalData!.start, - ), - ); - - state = .data( - .new( - timeline: direction == .forward - ? chat.timeline.addAll(paginationResponse.events) - : paginationResponse.events.addAll(chat.timeline), - hasMoreForward: - direction == .forward && paginationResponse.nextBatch == null - ? false - : chat.hasMoreForward, - hasMoreBackward: - direction == .backward && paginationResponse.nextBatch == null - ? false - : chat.hasMoreBackward, - historicalData: chat.historicalData?.copyWith( - start: - (direction == .backward - ? paginationResponse.nextBatch - : null) ?? - chat.historicalData!.start, - end: - (direction == .forward ? paginationResponse.nextBatch : null) ?? - chat.historicalData!.end, + Future loadOlder() async { + final timelineKeys = ref + .read(RoomsController.provider.select((value) => value[roomId])) + ?.timeline + .keys; + final response = await ref + .watch(ClientController.provider.notifier) + .paginate( + .new( + roomId: roomId, + maxTimelineId: timelineKeys?.isNotEmpty == true + ? timelineKeys?.reduce(min) + : null, ), - ), - ); - } + ); + + ref + .watch(RoomsController.provider.notifier) + .update( + IMap({ + roomId: Room( + events: IMap.fromIterable( + response.events.addAll(response.relatedEvents), + keyMapper: (event) => event.rowId, + valueMapper: (event) => event, + ), + hasMore: response.hasMore, + timeline: IMap.fromIterable( + response.events, + keyMapper: (event) => event.timelineRowId, + valueMapper: (event) => event.rowId, + ), + ), + }), + .new(), + ); + + return response.hasMore; } Future send( @@ -192,8 +125,8 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) if (relationType == .edit) { baseContent = relation?.content; } else { - final provider = AttachmentController.provider(info.$1); - baseContent = ref.read(provider)?.$2; + final provider = AttachmentController.provider(roomId); + baseContent = ref.watch(provider)?.$2; ref.invalidate(provider); } @@ -209,10 +142,10 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) ); } - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); final event = await client.sendMessage( SendMessageRequest( - roomId: info.$1, + roomId: roomId, baseContent: baseContent, mentions: Mentions( userIds: [ @@ -231,12 +164,12 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) ); ref - .read(RoomsController.provider.notifier) + .watch(RoomsController.provider.notifier) .update( .new({ - info.$1: .new( + roomId: .new( events: .new({event.rowId: event}), - clientSticky: .new({event.rowId}), + sticky: .new({event.rowId}), ), }), .new(), @@ -248,10 +181,10 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) Event event, String userId, ) async { - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); final allReactionEvents = await client.getRelatedEvents( .new( - roomId: info.$1, + roomId: roomId, eventId: event.eventId, relationType: "m.annotation", ), @@ -272,16 +205,16 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) if (reactionEvent != null) { await ref .watch(ClientController.provider.notifier) - .redactEvent(.new(eventId: reactionEvent.eventId, roomId: info.$1)); + .redactEvent(.new(eventId: reactionEvent.eventId, roomId: roomId)); } } Future sendReaction(String reaction, Event event) async { - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); await client.sendEvent( .new( - roomId: info.$1, + roomId: roomId, type: EventType.reaction.type, content: ReactionContent(key: reaction), synchronous: true, @@ -293,7 +226,7 @@ class RoomChatController(final (String roomId, String? contextualEvent) info) } static final provider = AsyncNotifierProvider.family - .autoDispose( + .autoDispose?, String>( RoomChatController.new, ); } diff --git a/lib/controllers/room_creators.dart b/lib/controllers/room_creators.dart index 09acdd7..7db72c2 100644 --- a/lib/controllers/room_creators.dart +++ b/lib/controllers/room_creators.dart @@ -4,7 +4,10 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/create.dart"; import "package:nexus/models/room.dart"; -class RoomCreatorsController(final Room room) extends Notifier> { +class RoomCreatorsController extends Notifier> { + final Room room; + RoomCreatorsController(this.room); + @override IList build() { final createRowId = room.state[EventType.create.type]?[""]; diff --git a/lib/controllers/room_summary.dart b/lib/controllers/room_summary.dart deleted file mode 100644 index e5b3174..0000000 --- a/lib/controllers/room_summary.dart +++ /dev/null @@ -1,16 +0,0 @@ -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/models/requests/join_room.dart"; -import "package:nexus/models/room_summary.dart"; - -class RoomSummaryController(final JoinRoomRequest request) - extends AsyncNotifier { - @override - Future build() => - ref.read(ClientController.provider.notifier).getRoomSummary(request); - - static final provider = AsyncNotifierProvider.family - .autoDispose( - RoomSummaryController.new, - ); -} diff --git a/lib/controllers/rooms.dart b/lib/controllers/rooms.dart index ff4d6a2..d0c6eb9 100644 --- a/lib/controllers/rooms.dart +++ b/lib/controllers/rooms.dart @@ -1,5 +1,4 @@ import "dart:isolate"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/models/event.dart"; @@ -47,10 +46,10 @@ class RoomsController extends Notifier> { roomId, existing?.copyWith( hasMore: incoming.hasMore, - clientSticky: - (incoming.clientSticky.isEmpty == true - ? existing.clientSticky - : existing.clientSticky.addAll(incoming.clientSticky)) + sticky: + (incoming.sticky.isEmpty == true + ? existing.sticky + : existing.sticky.addAll(incoming.sticky)) .removeWhere( (rowId) => incoming.timeline.values.contains(rowId), ), diff --git a/lib/controllers/settings.dart b/lib/controllers/settings.dart index 8c107e3..572d2e9 100644 --- a/lib/controllers/settings.dart +++ b/lib/controllers/settings.dart @@ -9,14 +9,14 @@ class SettingsController extends AsyncNotifier { final file = await ref.watch(SettingsFileController.provider.future); try { - return .fromJson(json.decode(await file.readAsString())); + return Settings.fromJson(json.decode(await file.readAsString())); } catch (_) { - return .new(); + return Settings(); } } Future set(Settings settings) async { - state = .data(settings); + state = AsyncData(settings); final file = await ref.watch(SettingsFileController.provider.future); await file.writeAsString(json.encode(settings.toJson())); } diff --git a/lib/controllers/settings_sections.dart b/lib/controllers/settings_sections.dart index 7fc1b3f..3c9a665 100644 --- a/lib/controllers/settings_sections.dart +++ b/lib/controllers/settings_sections.dart @@ -1,19 +1,13 @@ import "dart:io"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; import "package:m3e_buttons/m3e_buttons.dart"; import "package:nexus/controllers/account_data.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/notification.dart"; import "package:nexus/controllers/settings.dart"; -import "package:nexus/controllers/unified_push.dart"; -import "package:nexus/controllers/spec_versions.dart"; -import "package:nexus/controllers/unified_push_allowed.dart"; import "package:nexus/models/account_data.dart"; import "package:nexus/models/settings_category.dart"; import "package:nexus/main.dart"; @@ -24,6 +18,9 @@ class SettingsSectionsController @override Future>> build() async { final settings = await ref.watch(SettingsController.provider.future); + final specVersionsResponse = await ref + .watch(ClientController.provider.notifier) + .getSpecVersions(); return .new({ "General": .new([ @@ -52,7 +49,8 @@ class SettingsSectionsController .new( title: "Use Dynamic Theme", icon: Icons.palette, - description: "Toggle on or off Dynamic Theme. Only available on Android, Linux, Windows, or MacOS.", + description: + "Toggle on or off Dynamic Theme. Only available on Android, Linux, Windows, or MacOS.", builder: (title, description, icon) => SwitchListTile( title: Text(title), subtitle: Text(description), @@ -78,7 +76,8 @@ class SettingsSectionsController settings: .new([ .new( title: "Linux Mobile Mode", - description: "Enables some fixes for Linux mobile, e.g. disabling dragging appbar for moving window.", + description: + "Enables some fixes for Linux mobile, e.g. disabling dragging appbar for moving window.", icon: Icons.construction, builder: (title, description, icon) => SwitchListTile( title: Text(title), @@ -99,141 +98,46 @@ class SettingsSectionsController if (ref.watch(ClientStateController.provider)?.isLoggedIn == true) "Account": .new([ .new(title: "Profile", icon: Icons.person, settings: .new([])), - .new( - title: "Notifications", - icon: Icons.notifications, - settings: .new([ - .new( - title: "Push notifications", - description: "Enable push notifications using Web Push", - builder: (title, description, icon) => HookConsumer( - builder: (context, ref, _) { - final loading = useState(false); - final pusherRegistered = ref.watch( - UnifiedPushController.provider, - ); - final unifiedPushAllowed = ref.watch( - UnifiedPushAllowedController.provider, - ); - - return SwitchListTile( - title: Text(title), - subtitle: Text( - unifiedPushAllowed.maybeWhen( - data: (data) => data, - orElse: () => null, - ) ?? - description, - ), - secondary: Icon(icon), - value: pusherRegistered.maybeWhen( - data: (value) => value, - orElse: () => false, - ), - onChanged: loading.value - ? null - : unifiedPushAllowed.maybeWhen( - data: (data) => data == null, - orElse: () => false, - ) - ? pusherRegistered.maybeWhen( - data: (_) => (value) async { - try { - loading.value = true; - if (value) { - if (await ref - .watch( - NotificationController - .provider - .notifier, - ) - .requestPermissions()) { - await ref - .watch( - UnifiedPushController - .provider - .notifier, - ) - .register(); - } else { - // TODO: Handle not granted - } - } else { - await ref - .watch( - UnifiedPushController - .provider - .notifier, - ) - .deregister(); - } - } catch (error, stackTrace) { - showError(error, stackTrace); - } finally { - loading.value = false; - } - }, - orElse: () => null, - ) - : null, - ); - }, - ), - icon: Icons.notification_add, - ), - ]), - ), .new( title: "Safety", icon: Icons.gpp_good, settings: .new([ .new( title: "Invite Blocking", - description: "Block invites, either completely, or block only invites from users without shared private rooms (depends on server support).", + description: + "Block invites, either completely, or block only invites from users without shared private rooms (depends on server support).", builder: (title, description, icon) => Consumer( - builder: (context, ref, _) { - final specVersionsResponse = ref.watch( - SpecVersionsController.provider, - ); - return DialogListTile( - icon: Icon(icon), - title: title, - subtitle: Text(description), - initialValue: ref - .watch(AccountDataController.provider) - .invitePermissionConfig - .defaultAction, - options: - specVersionsResponse.maybeWhen( - data: (data) => data.unstableFeatures.msc4494, - orElse: () => true, - ) - ? DefaultInviteAction.values - : IList(DefaultInviteAction.values) - .remove(.denyPublic) - .toList(), - getName: (option) => switch (option) { - .allow => "Allow", - .deny => "Deny", - .denyPublic => "Deny public", - }, - onChanged: specVersionsResponse.maybeWhen( - data: (_) => - (value) => ref - .watch(ClientController.provider.notifier) - .setAccountData( - .new( - type: AccountData.invitePermissionConfigKey, - content: InvitePermissionConfig( - defaultAction: value, - ), - ), - ) - .onError(showError), - orElse: () => null, + builder: (context, ref, _) => + DialogListTile( + icon: Icon(icon), + title: title, + subtitle: Text(description), + initialValue: ref + .watch(AccountDataController.provider) + .invitePermissionConfig + .defaultAction, + options: specVersionsResponse.unstableFeatures.msc4494 + ? DefaultInviteAction.values + : IList( + DefaultInviteAction.values, + ).remove(.denyPublic).toList(), + getName: (option) => switch (option) { + .allow => "Allow", + .deny => "Deny", + .denyPublic => "Deny public", + }, + onChanged: (value) => ref + .watch(ClientController.provider.notifier) + .setAccountData( + .new( + type: InvitePermissionConfig.key, + content: InvitePermissionConfig( + defaultAction: value, + ), + ), + ) + .onError(showError), ), - ); - }, ), icon: Icons.person_off, ), @@ -252,14 +156,15 @@ class SettingsSectionsController final colorScheme = Theme.of(context).colorScheme; return M3EButton.icon( onPressed: () async { - await ref - .watch(UnifiedPushController.provider.notifier) - .deregister() - .onError(showError); + Navigator.of( + context, + ).popUntil((route) => route.isFirst); + + await WidgetsBinding.instance.endOfFrame; + await ref .watch(ClientController.provider.notifier) - .logout() - .onError(showError); + .logout(); }, label: Text(title), icon: Icon(icon), diff --git a/lib/controllers/shared_prefs.dart b/lib/controllers/shared_prefs.dart index 9ecb34a..876fc47 100644 --- a/lib/controllers/shared_prefs.dart +++ b/lib/controllers/shared_prefs.dart @@ -1,12 +1,12 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:shared_preferences/shared_preferences.dart"; -class SharedPrefsController extends Notifier { +class SharedPrefsController extends AsyncNotifier { @override - SharedPreferencesAsync build() => SharedPreferencesAsync(); + Future build() async => .getInstance(); static final provider = - NotifierProvider( + AsyncNotifierProvider( SharedPrefsController.new, ); } diff --git a/lib/controllers/spaces.dart b/lib/controllers/spaces.dart index 7dc14b0..60696da 100644 --- a/lib/controllers/spaces.dart +++ b/lib/controllers/spaces.dart @@ -1,6 +1,6 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/account_data.dart"; import "package:nexus/controllers/rooms.dart"; diff --git a/lib/controllers/spec_versions.dart b/lib/controllers/spec_versions.dart deleted file mode 100644 index d2316bd..0000000 --- a/lib/controllers/spec_versions.dart +++ /dev/null @@ -1,15 +0,0 @@ -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/models/spec_versions_response.dart"; - -class SpecVersionsController extends AsyncNotifier { - @override - Future build() => - ref.read(ClientController.provider.notifier).getSpecVersions(); - - static final provider = - AsyncNotifierProvider.autoDispose< - SpecVersionsController, - SpecVersionsResponse - >(SpecVersionsController.new); -} diff --git a/lib/controllers/unified_push.dart b/lib/controllers/unified_push.dart deleted file mode 100644 index db99868..0000000 --- a/lib/controllers/unified_push.dart +++ /dev/null @@ -1,178 +0,0 @@ -import "dart:async"; -import "dart:convert"; -import "dart:io"; - -import "package:flutter/foundation.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:intl/intl.dart"; -import "package:nexus/controllers/key.dart"; -import "package:nexus/controllers/notification.dart"; -import "package:nexus/controllers/push_key.dart"; -import "package:nexus/main.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/models/content/message.dart"; -import "package:nexus/models/content/sticker.dart"; -import "package:unifiedpush/unifiedpush.dart"; -import "package:unifiedpush_storage_shared_preferences/storage.dart"; -import "package:window_manager/window_manager.dart"; - -class UnifiedPushController extends AsyncNotifier { - @override - Future build() async { - if (!Platform.isLinux && !Platform.isAndroid) return false; - - final registered = await UnifiedPush.initialize( - linuxOptions: .new( - dbusName: "nexus.federated.nexus.UnifiedPush", - storage: UnifiedPushStorageSharedPreferences(), - background: isInBackground, - shouldWriteService: false, - ), - onNewEndpoint: (endpoint, instance) async { - final pushKey = endpoint.pubKeySet!.pubKey; - await ref - .read(PushKeyController.provider(instance).notifier) - .set(pushKey); - - await ref - .read(ClientController.provider.notifier) - .registerPusher( - .new( - appDisplayName: "Nexus", - appId: "nexus.federated.nexus", - data: .webPush( - url: .parse(endpoint.url), - auth: endpoint.pubKeySet!.auth, - ), - deviceDisplayName: - "Nexus on ${toBeginningOfSentenceCase(Platform.operatingSystem)}", - kind: .webPush, - lang: "en", - pushKey: pushKey, - ), - ); - - state = .data(true); - }, - onMessage: (message, instance) async { - debugPrint("UP message received for $instance"); - if (message.decrypted == false) { - throw Exception( - "Failed to decrypt notification. Try toggling off and on UnifiedPush in settings.", - ); - } - final (event, roomMetadata) = await ref - .read(ClientController.provider.notifier) - .handlePush(json.decode(String.fromCharCodes(message.content))); - - if (event.unreadType?.shouldNotify() != true || - (!isInBackground && - await windowManager.isFocused().onError((_, _) => true) && - await ref.read( - KeyController.provider(KeyController.roomKey).future, - ) == - event.roomId)) { - if (isInBackground) exit(0); - return; - } - - final icon = roomMetadata.avatar == null - ? null - : await ref - .read(ClientController.provider.notifier) - .downloadMedia( - .new(mxc: roomMetadata.avatar!, isAvatar: true), - ); - - await ref - .read(NotificationController.provider.notifier) - .send( - id: event.eventId.hashCode & 0x7fffffff, - title: roomMetadata.name ?? "New Event", - icon: icon, - payload: event.eventId, - body: switch (event.content) { - MessageContent(:final body?) || - StickerContent(:final body) => body, - _ => null, - }, - ); - - if (isInBackground) exit(0); - }, - onUnregistered: deregister, - ); - - ref.listen( - ClientStateController.provider.select((value) => value?.deviceId), - (_, _) => register(), - ); - - if (registered) { - // Needs to be registered every startup - await register(); - } - - return registered; - } - - Future register() async { - state = .loading(); - try { - final deviceId = ref.read( - ClientStateController.provider.select((value) => value?.deviceId), - ); - if (deviceId == null) return; - - final capabilities = await ref - .read(ClientController.provider.notifier) - .getCapabilities(); - - if (capabilities.webpush?.vapid == null) { - throw UnsupportedError( - "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.", - ); - } - - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { - throw Exception("No UnifiedPush distributors found."); - } - - await UnifiedPush.register( - instance: deviceId, - vapid: capabilities.webpush?.vapid, - ); - } catch (_) { - state = .data(false); - rethrow; - } - } - - Future deregister([String? instance]) async { - final clientState = ref.read(ClientStateController.provider); - - final keyProvider = PushKeyController.provider( - instance ?? clientState!.deviceId!, - ); - final key = await ref.read(keyProvider.future); - - if (key != null) { - await ref - .read(ClientController.provider.notifier) - .deregisterPusher(.new(appId: "nexus.federated.nexus", pushKey: key)); - await ref.read(keyProvider.notifier).set(null); - } else { - debugPrint( - "No matching pushKey found. Skipping deregistration from homeserver.", - ); - } - - await UnifiedPush.unregister(instance ?? clientState!.deviceId!); - state = .data(false); - } - - static final provider = AsyncNotifierProvider( - UnifiedPushController.new, - ); -} diff --git a/lib/controllers/unified_push_allowed.dart b/lib/controllers/unified_push_allowed.dart deleted file mode 100644 index 8f7fd8e..0000000 --- a/lib/controllers/unified_push_allowed.dart +++ /dev/null @@ -1,33 +0,0 @@ -import "dart:io"; - -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:unifiedpush/unifiedpush.dart"; - -class UnifiedPushAllowedController extends AsyncNotifier { - @override - Future build() async { - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { - return "No valid distributors found. ${Platform.isLinux - ? "Try installing KUnifiedPush" - : Platform.isAndroid - ? "Try installing Google Play Services or NTFY" - : "Your platform is not currently supported by UnifiedPush"}."; - } - - final capabilities = await ref - .watch(ClientController.provider.notifier) - .getCapabilities(); - - if (capabilities.webpush?.vapid == null) { - return "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus."; - } - - return null; - } - - static final provider = - AsyncNotifierProvider.autoDispose( - UnifiedPushAllowedController.new, - ); -} diff --git a/lib/controllers/url_preview.dart b/lib/controllers/url_preview.dart index 41c3ef4..1b17870 100644 --- a/lib/controllers/url_preview.dart +++ b/lib/controllers/url_preview.dart @@ -3,8 +3,10 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/open_graph_data.dart"; -class UrlPreviewController(final Uri url) - extends AsyncNotifier { +class UrlPreviewController extends AsyncNotifier { + final Uri url; + UrlPreviewController(this.url); + @override Future build() async { if (url.host == "matrix.to") return null; diff --git a/lib/controllers/via.dart b/lib/controllers/via.dart index 39db682..d9227ba 100644 --- a/lib/controllers/via.dart +++ b/lib/controllers/via.dart @@ -7,7 +7,10 @@ import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/room.dart"; -class ViaController(final Room room) extends Notifier { +class ViaController extends Notifier { + final Room room; + ViaController(this.room); + @override String build() { final servers = {}; diff --git a/lib/helpers/extensions/build_event_options.dart b/lib/helpers/extensions/build_event_options.dart deleted file mode 100644 index a95da6d..0000000 --- a/lib/helpers/extensions/build_event_options.dart +++ /dev/null @@ -1,266 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; -import "package:flutter/services.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/pinned_ids.dart"; -import "package:nexus/controllers/power_level.dart"; -import "package:nexus/controllers/recent_emoji.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/controllers/room_chat.dart"; -import "package:nexus/controllers/via.dart"; -import "package:nexus/models/content/message.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/models/relation_type.dart"; -import "package:nexus/widgets/emoji_picker.dart"; -import "package:nexus/main.dart"; - -extension BuildEventOptions on Event { - IList buildEventOptions({ - required BuildContext context, - required WidgetRef ref, - required String roomId, - required String userId, - required void Function(Event, RelationType) onRelation, - }) { - final theme = Theme.of(context); - final danger = theme.colorScheme.error; - - final notifier = ref.read( - RoomChatController.provider((roomId, null)).notifier, - ); - final client = ref.read(ClientController.provider.notifier); - - final isPinned = ref - .watch(PinnedIdsController.provider(roomId)) - .contains(eventId); - - Future sendReaction(String emoji) async { - await notifier.sendReaction(emoji, this).onError(showError); - - await ref - .read(RecentEmojiController.provider.notifier) - .add(emoji) - .onError(showError); - } - - void showReasonDialog({ - required String title, - required String description, - required String action, - required Future Function(String reason) onConfirm, - }) { - showDialog( - context: context, - builder: (context) => HookBuilder( - builder: (context) { - final reasonController = useTextEditingController(); - - return AlertDialog( - title: Text(title), - content: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - Text(description), - const SizedBox(height: 12), - TextField( - controller: reasonController, - textCapitalization: .sentences, - decoration: .new(labelText: "Reason (optional)"), - ), - ], - ), - actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: const Text("Cancel"), - ), - TextButton( - onPressed: () { - final reason = reasonController.text; - Navigator.of(context).pop(); - - onConfirm(reason).onError(showError); - }, - child: Text(action), - ), - ], - ); - }, - ), - ); - } - - return .new([ - if (ref.watch( - PowerLevelController.provider( - .new(eventType: .reaction, roomId: roomId), - ), - )) - PopupMenuItem( - enabled: false, - child: IconTheme( - data: theme.iconTheme, - child: Row( - children: [ - for (final emoji in { - ...ref - .watch(RecentEmojiController.provider) - .map((entry) => entry.emoji), - "👍", - "🤣", - "😭", - "🤔", - }.take(4)) - IconButton( - icon: Text(emoji), - onPressed: () { - Navigator.of(context).pop(); - sendReaction(emoji); - }, - ), - IconButton( - icon: const Icon(Icons.emoji_emotions), - onPressed: () { - Navigator.of(context).pop(); - - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => EmojiPicker( - allowFreeText: true, - onSelection: (emoji) { - Navigator.of(context).pop(); - sendReaction(emoji); - }, - ), - ); - }, - ), - ], - ), - ), - ), - - if (ref.watch( - PowerLevelController.provider( - .new(eventType: .message, roomId: roomId), - ), - )) - PopupMenuItem( - onTap: () => onRelation(this, .reply), - child: const ListTile( - leading: Icon(Icons.reply), - title: Text("Reply"), - ), - ), - - if (content is MessageContent && sender == userId) - PopupMenuItem( - onTap: () => onRelation(this, .edit), - child: const ListTile(leading: Icon(Icons.edit), title: Text("Edit")), - ), - - if (ref.watch( - PowerLevelController.provider( - .state(eventType: .pinnedEvents, roomId: roomId), - ), - )) - PopupMenuItem( - onTap: () async { - try { - final pins = ref.read( - PinnedIdsController.provider(roomId).notifier, - ); - - if (isPinned) { - await pins.removePin(eventId); - } else { - await pins.addPin(eventId); - } - } catch (error, stackTrace) { - showError(error, stackTrace); - } - }, - child: ListTile( - leading: const Icon(Icons.push_pin), - title: Text(isPinned ? "Unpin Event" : "Pin Event"), - ), - ), - - PopupMenuItem( - onTap: () async { - final room = ref.read( - RoomsController.provider.select((rooms) => rooms[roomId]), - ); - if (room == null) return; - - final vias = ref.read(ViaController.provider(room)); - - await Clipboard.setData( - ClipboardData( - text: - "matrix:roomid/${room.metadata?.id.substring(1)}/e/$eventId$vias", - ), - ); - }, - child: const ListTile( - leading: Icon(Icons.link), - title: Text("Copy Link"), - ), - ), - - if (content case MessageContent(:final body?)) - PopupMenuItem( - onTap: () => Clipboard.setData(ClipboardData(text: body)), - child: const ListTile( - leading: Icon(Icons.copy), - title: Text("Copy Text"), - ), - ), - - if (ref.watch( - PowerLevelController.provider( - .redaction(targetUser: sender, roomId: roomId), - ), - )) - PopupMenuItem( - onTap: () => showReasonDialog( - title: "Delete Message", - description: - "Are you sure you want to delete this message? " - "This cannot be reversed.", - action: "Delete", - onConfirm: (reason) => notifier.deleteMessage(this, reason: reason), - ), - child: ListTile( - leading: Icon(Icons.delete, color: danger), - title: Text("Delete", style: .new(color: danger)), - ), - ), - - PopupMenuItem( - onTap: () => showReasonDialog( - title: "Report", - description: - "Report this this to your server administrators, " - "who can take action like banning this server or room.", - action: "Report", - onConfirm: (reason) => client.reportEvent( - .new( - roomId: roomId, - eventId: eventId, - reason: reason.isEmpty ? null : reason, - ), - ), - ), - child: ListTile( - leading: Icon(Icons.report, color: danger), - title: Text("Report", style: .new(color: danger)), - ), - ), - ]); - } -} diff --git a/lib/helpers/extensions/get_xcode_sdk.dart b/lib/helpers/extensions/get_xcode_sdk.dart index 58920c8..4c09246 100644 --- a/lib/helpers/extensions/get_xcode_sdk.dart +++ b/lib/helpers/extensions/get_xcode_sdk.dart @@ -1,14 +1,11 @@ import "dart:io"; -Future getXCodeTool({String? sdkType, String? findTool}) async { - final result = await Process.run("xcrun", [ - if (sdkType != null) ...["--sdk", sdkType], - if (findTool != null) ...["-f", findTool] else "--show-sdk-path", - ]); +Future getXCodeSDK() async { + final result = await Process.run("xcrun", ["--show-sdk-path"]); if (result.exitCode != 0) { - throw Exception("Failed to get ${sdkType ?? "XCode"} ${findTool ?? "SDK"}"); + throw Exception("Failed to get XCode SDK\n${result.stderr}"); } - return result.stdout.toString().trim(); + return result.stdout.trim(); } diff --git a/lib/helpers/extensions/link_to_mention.dart b/lib/helpers/extensions/link_to_mention.dart index 8595669..f4868d3 100644 --- a/lib/helpers/extensions/link_to_mention.dart +++ b/lib/helpers/extensions/link_to_mention.dart @@ -9,7 +9,6 @@ extension LinkToMention on String { /// /// Returns the decoded identifier (e.g. "#room:matrix.org") /// or null if this is not a Matrix link. - /// TODO: Needs to be reworked to handle event links. Might be worth just rewriting, I don't like this code. String? get mention { final trimmed = trim(); diff --git a/lib/helpers/extensions/scheme_to_theme.dart b/lib/helpers/extensions/scheme_to_theme.dart index 6beca04..bf48535 100644 --- a/lib/helpers/extensions/scheme_to_theme.dart +++ b/lib/helpers/extensions/scheme_to_theme.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; extension SchemeToTheme on ColorScheme { ThemeData get theme { diff --git a/lib/helpers/extensions/show_about_dialog.dart b/lib/helpers/extensions/show_about_dialog.dart index 6ccba4c..2f24a3e 100644 --- a/lib/helpers/extensions/show_about_dialog.dart +++ b/lib/helpers/extensions/show_about_dialog.dart @@ -1,11 +1,11 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_card_list/m3e_card_list.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:package_info_plus/package_info_plus.dart"; -extension ShowAboutDialog on BuildContext { +extension ShowContextMenu on BuildContext { Future showAboutDialog(WidgetRef ref) async { final packageInfo = await PackageInfo.fromPlatform(); @@ -20,7 +20,7 @@ extension ShowAboutDialog on BuildContext { Row( spacing: 12, children: [ - SvgPicture.asset("assets/bundled/icon.svg", width: 64), + SvgPicture.asset("assets/icon.svg", width: 64), Expanded( child: Column( crossAxisAlignment: .start, @@ -50,8 +50,8 @@ extension ShowAboutDialog on BuildContext { M3ECardColumn( onTap: (index) => ref.watch(LaunchHelper.provider).launchUrl(switch (index) { - 0 => .https("git.federated.nexus", "nexus/nexus"), - _ => .https("liberapay.com", "QuadRadical"), + 0 => Uri.https("git.federated.nexus", "nexus/nexus"), + _ => Uri.https("liberapay.com", "QuadRadical"), }), children: [ ListTile( diff --git a/lib/helpers/extensions/show_context_menu.dart b/lib/helpers/extensions/show_context_menu.dart index ccef3b5..c860115 100644 --- a/lib/helpers/extensions/show_context_menu.dart +++ b/lib/helpers/extensions/show_context_menu.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; extension ShowContextMenu on BuildContext { void showContextMenu({ @@ -9,7 +9,7 @@ extension ShowContextMenu on BuildContext { showMenu( context: this, - constraints: .loose(.infinite), + constraints: .loose(Size.infinite), position: .fromLTRB( globalPosition.dx, globalPosition.dy, diff --git a/lib/helpers/extensions/show_user_popover.dart b/lib/helpers/extensions/show_user_popover.dart index f42cd57..1ea3015 100644 --- a/lib/helpers/extensions/show_user_popover.dart +++ b/lib/helpers/extensions/show_user_popover.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/widgets/user_bottom_sheet.dart"; diff --git a/lib/helpers/extensions/string_to_color.dart b/lib/helpers/extensions/string_to_color.dart index a21ca82..eaa7714 100644 --- a/lib/helpers/extensions/string_to_color.dart +++ b/lib/helpers/extensions/string_to_color.dart @@ -1,5 +1,5 @@ import "package:color_hash/color_hash.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; extension ToColor on String { Color get colorHash => ColorHash(this, lightness: .5, saturation: .7).color; diff --git a/lib/helpers/hooks/chat_scroll.dart b/lib/helpers/hooks/chat_scroll.dart deleted file mode 100644 index 3b1531eb..0000000 --- a/lib/helpers/hooks/chat_scroll.dart +++ /dev/null @@ -1,203 +0,0 @@ -import "dart:async"; - -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/models/direction.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/models/room_chat.dart"; - -final class ChatScroll({ - required final IList historyItems, - required final IList liveItems, - required final GlobalKey centerKey, - required final ScrollController scrollController, - required final bool atBottom, - required final Future Function(String id) jumpToId, - required final Future Function() jumpToBottom, - required final GlobalKey Function(String eventId) keyFor, -}) { - factory use({ - required AsyncValue controllerData, - required Future Function(Direction direction) paginate, - required Future Function() markRead, - required ValueNotifier contextualEvent, - }) { - final anchorId = useState(null); - - final itemKeys = useMemoized(() => {}, []); - GlobalKey keyFor(String eventId) => - itemKeys.putIfAbsent(eventId, GlobalKey.new); - - final scrollController = useScrollController(); - final centerKey = useMemoized(GlobalKey.new); - - final atBottom = useState(true); - - final pendingAnchorTarget = useState(null); - final anchorMountedCompleter = useRef?>(null); - - useEffect(() { - if (anchorId.value == null) { - if (controllerData case AsyncData(:final value?) - when value.timeline.isNotEmpty) { - final hasContextualEvent = value.timeline.any( - (event) => event.eventId == contextualEvent.value, - ); - - anchorId.value = hasContextualEvent - ? contextualEvent.value - : value.timeline.last.eventId; - } - } - - return null; - }, [controllerData, contextualEvent.value]); - - useEffect(() { - final target = pendingAnchorTarget.value; - if (target == null) return null; - - final found = - controllerData.value?.timeline.any( - (event) => event.eventId == target, - ) ?? - false; - - if (found || controllerData is AsyncError) { - if (found) { - anchorId.value = target; - - WidgetsBinding.instance.addPostFrameCallback((_) { - final context = keyFor(target).currentContext; - if (context != null && context.mounted) { - anchorMountedCompleter.value?.complete(context); - anchorMountedCompleter.value = null; - } - }); - } else { - anchorMountedCompleter.value?.completeError( - StateError("Failed to load context for $target"), - ); - anchorMountedCompleter.value = null; - } - pendingAnchorTarget.value = null; - } - - return null; - }, [controllerData, pendingAnchorTarget.value]); - - final ({IList history, IList live}) split = useMemoized(() { - final items = controllerData.value?.timeline; - final anchor = anchorId.value; - - if (items == null || anchor == null) { - return (history: const .empty(), live: const .empty()); - } - - final anchorIndex = items.indexWhere((item) => item.eventId == anchor); - - if (anchorIndex == -1) { - return (history: const .empty(), live: items); - } - - return ( - history: items.take(anchorIndex).toIList().reversed.toIList(), - live: items.skip(anchorIndex).toIList(), - ); - }, [controllerData, anchorId.value]); - - useEffect( - () { - const loadThreshold = 500.0; - const readThreshold = 50.0; - - Future checkPosition() async { - if (!scrollController.hasClients) return; - - final position = scrollController.position; - - final isAtBottom = position.extentBefore <= readThreshold; - if (isAtBottom != atBottom.value) atBottom.value = isAtBottom; - - if (position.extentAfter <= loadThreshold) { - await paginate(.backward); - } else if (contextualEvent.value != null && - position.extentBefore <= loadThreshold) { - await paginate(.forward); - } else if (position.extentBefore <= readThreshold) { - await markRead(); - } - } - - scrollController.addListener(checkPosition); - - WidgetsBinding.instance.addPostFrameCallback((_) => checkPosition()); - - return () => scrollController.removeListener(checkPosition); - }, - [ - scrollController, - controllerData, - paginate, - markRead, - contextualEvent.value, - ], - ); - - return .new( - historyItems: split.history, - liveItems: split.live, - centerKey: centerKey, - scrollController: scrollController, - atBottom: atBottom.value, - jumpToId: (String itemId) async { - if (!scrollController.hasClients) return; - - final existing = keyFor(itemId).currentContext; - if (existing != null && existing.mounted) { - // Already mounted, just scroll - await Scrollable.ensureVisible( - existing, - alignment: 0.5, - duration: const .new(milliseconds: 700), - curve: Curves.easeInOut, - ); - } else { - final completer = Completer(); - anchorMountedCompleter.value = completer; - pendingAnchorTarget.value = itemId; - contextualEvent.value = itemId; - - final context = await completer.future; - if (!context.mounted) return; - - await Scrollable.ensureVisible(context, alignment: 10); - if (!context.mounted) return; - await Scrollable.ensureVisible( - context, - alignment: 0.5, - duration: const .new(milliseconds: 700), - curve: Curves.easeOutCirc, - ); - } - }, - jumpToBottom: () async { - if (contextualEvent.value != null) { - anchorId.value = null; - contextualEvent.value = null; - } - - if (!scrollController.hasClients) return; - - await scrollController.animateTo( - scrollController.position.minScrollExtent, - duration: const .new(milliseconds: 700), - curve: Curves.easeInOut, - ); - }, - keyFor: keyFor, - ); - } -} diff --git a/lib/helpers/launch_helper.dart b/lib/helpers/launch_helper.dart index 89c7015..575395f 100644 --- a/lib/helpers/launch_helper.dart +++ b/lib/helpers/launch_helper.dart @@ -2,7 +2,10 @@ import "package:flutter/services.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:url_launcher/url_launcher.dart" as ul; -class LaunchHelper(Ref ref) { +class LaunchHelper { + final Ref ref; + LaunchHelper(this.ref); + Future launchUrl(Uri url, {bool useWebview = false}) async { try { return await ul.launchUrl( diff --git a/lib/helpers/mxc_image.dart b/lib/helpers/mxc_image.dart index 83a345a..3adb10e 100644 --- a/lib/helpers/mxc_image.dart +++ b/lib/helpers/mxc_image.dart @@ -1,12 +1,14 @@ import "dart:ui"; - import "package:flutter/widgets.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/requests/download_media.dart"; -class MxcImage(final WidgetRef ref, final DownloadMediaRequest request) - extends ImageProvider { +class MxcImage extends ImageProvider { + final WidgetRef ref; + final DownloadMediaRequest request; + const MxcImage(this.ref, this.request); + @override Future obtainKey(ImageConfiguration configuration) => Future.value(this); diff --git a/lib/main.dart b/lib/main.dart index 5586cbf..53cc57b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,31 +1,26 @@ import "dart:io"; - import "package:dynamic_color/dynamic_color.dart"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:media_kit/media_kit.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/gomuks_listener.dart"; -import "package:nexus/controllers/key.dart"; -import "package:nexus/controllers/member_list_opened.dart"; import "package:nexus/controllers/multi_provider.dart"; -import "package:nexus/controllers/notification.dart"; import "package:nexus/controllers/settings.dart"; -import "package:nexus/controllers/unified_push.dart"; +import "package:nexus/controllers/shared_prefs.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/scheme_to_theme.dart"; import "package:nexus/helpers/font_licenses.dart"; -import "package:nexus/widgets/pages/chat.dart"; -import "package:nexus/widgets/pages/select_server.dart"; -import "package:nexus/widgets/pages/settings.dart"; -import "package:nexus/widgets/pages/verify.dart"; -import "package:nexus/widgets/appbar.dart"; +import "package:nexus/pages/chat.dart"; +import "package:nexus/pages/select_server.dart"; +import "package:nexus/pages/verify.dart"; import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; import "package:window_manager/window_manager.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; final GlobalKey navigatorKey = GlobalKey(); -late final bool isInBackground; final class Logger extends ProviderObserver { @override @@ -42,7 +37,14 @@ New Value: ${newValue is AsyncData ? newValue.value : newValue} } void showError(Object error, [StackTrace? stackTrace]) { - if (error.toString().contains("'_nextFrame != null': is not true.")) { + if (error.toString().contains("DioException") || + error.toString().contains( + "setState() or markNeedsBuild() called during build.", + ) || + error.toString().contains("Invalid source") || + error.toString().contains("UTF-16") || + error.toString().contains("HTTP request failed") || + error.toString().contains("Invalid image data")) { return; } @@ -59,59 +61,49 @@ void showError(Object error, [StackTrace? stackTrace]) { } } -void main(List args) async { +void main() async { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); - LicenseRegistry.addLicense(() => .fromIterable(fontLicenses)); + if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { + await windowManager.ensureInitialized(); + await windowManager.waitUntilReadyToShow( + WindowOptions( + titleBarStyle: TitleBarStyle.hidden, + windowButtonVisibility: false, + ), + ); + await windowManager.setMinimumSize(Size.square(500)); + } + + LicenseRegistry.addLicense(() => Stream.fromIterable(fontLicenses)); FlutterError.onError = (FlutterErrorDetails details) => showError(details.exception.toString(), details.stack); - isInBackground = - Platform.environment["FLUTTER_HEADLESS"] != null || - args.contains("--unifiedpush-bg"); - - if (isInBackground) { - await ProviderContainer() - .read(UnifiedPushController.provider.future) - .timeout(Duration(seconds: 5)); - - await Future.delayed(Duration(seconds: 10)); - exit(0); - } else { - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { - await windowManager.ensureInitialized(); - await windowManager.waitUntilReadyToShow( - WindowOptions( - titleBarStyle: TitleBarStyle.hidden, - windowButtonVisibility: false, - ), - ); - await windowManager.setMinimumSize(Size.square(500)); - } - - runApp( - ProviderScope( - retry: (_, _) => null, - observers: [ - // Change false to true if you want debug information on provider reloads - // ignore: dead_code - if (false && kDebugMode) Logger(), - ], - child: App(), - ), - ); - } + runApp( + ProviderScope( + retry: (_, _) => null, + observers: [ + // Change false to true if you want debug information on provider reloads + // ignore: dead_code + if (false && kDebugMode) Logger(), + ], + child: const App(), + ), + ); } -class const App({super.key}) extends StatelessWidget { +class App extends StatelessWidget { + const App({super.key}); + @override Widget build(BuildContext context) => DynamicColorBuilder( builder: (lightDynamic, darkDynamic) => Consumer( builder: (context, ref, child) => MaterialApp( navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, + // Use indigo to work around bugs in theme generation theme: (ref .watch(SettingsController.provider) @@ -120,7 +112,7 @@ class const App({super.key}) extends StatelessWidget { data: (settings) => settings.useDynamicTheming ? lightDynamic : null, ) ?? - ThemeData.light().colorScheme) + ColorScheme.fromSeed(seedColor: Colors.indigo)) .theme, darkTheme: (ref @@ -130,7 +122,10 @@ class const App({super.key}) extends StatelessWidget { data: (settings) => settings.useDynamicTheming ? darkDynamic : null, ) ?? - ThemeData.dark().colorScheme) + ColorScheme.fromSeed( + seedColor: Colors.indigo, + brightness: Brightness.dark, + )) .theme, themeMode: ref .watch(SettingsController.provider) @@ -142,55 +137,36 @@ class const App({super.key}) extends StatelessWidget { ), child: Scaffold( body: Consumer( - builder: (_, ref, _) => switch (ref.watch( - MultiProviderController.provider( - .new([ - GomuksListenerController.provider, - NotificationController.provider, - UnifiedPushController.provider, - MemberListOpenedController.provider, - KeyController.provider(KeyController.roomKey), - KeyController.provider(KeyController.spaceKey), - ]), - ), - )) { - AsyncData(value: _) || AsyncLoading(value: _?) => Consumer( - builder: (_, ref, _) { - final clientState = ref.watch(ClientStateController.provider); + builder: (_, ref, _) => ref + .watch( + MultiProviderController.provider( + IListConst([ + SharedPrefsController.provider, + ClientController.provider, + ]), + ), + ) + .betterWhen( + data: (_) => Consumer( + builder: (_, ref, _) { + final clientState = ref.watch( + ClientStateController.provider, + ); - if (clientState == null || !clientState.isInitialized) { - return Loading(); - } + if (clientState == null || !clientState.isInitialized) { + return Loading(); + } - if (!clientState.isLoggedIn) { - return SelectServerPage(); - } else if (!clientState.isVerified) { - return VerifyPage(); - } else { - return ChatPage(); - } - }, - ), - - AsyncLoading _ => Scaffold( - appBar: Appbar( - actions: .new([ - IconButton( - onPressed: () => showDialog( - context: context, - builder: (_) => SettingsPage(), - ), - icon: Icon(Icons.settings), - ), - ]), + if (!clientState.isLoggedIn) { + return SelectServerPage(); + } else if (!clientState.isVerified) { + return VerifyPage(); + } else { + return ChatPage(); + } + }, + ), ), - body: Loading(), - ), - AsyncError(:final error, :final stackTrace) => ErrorDialog( - error, - stackTrace, - ), - }, ), ), ), diff --git a/lib/models/account_data.dart b/lib/models/account_data.dart index 955d8d1..96ba6a7 100644 --- a/lib/models/account_data.dart +++ b/lib/models/account_data.dart @@ -1,60 +1,60 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "account_data.freezed.dart"; part "account_data.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const AccountData({ - @JsonKey(name: AccountData.invitePermissionConfigKey) - final InvitePermissionConfig invitePermissionConfig = - const InvitePermissionConfig(), +@freezed +abstract class AccountData with _$AccountData { + const AccountData._(); + static List? readRecentEmojiValue( + Map json, + String key, + ) => json[key]?["recent_emoji"]; - @JsonKey(name: AccountData.directKey) - final IMap> directMessages = const IMap.empty(), + static Map>? recentEmojiToJson( + IList recentEmoji, + ) => {"recent_emoji": recentEmoji.map((emoji) => emoji.toJson()).toList()}; - @JsonKey(name: AccountData.recentEmojiKey) - final RecentEmojiData recentEmoji = const RecentEmojiData(), -}) with _$AccountData { - static const invitePermissionConfigKey = "m.invite_permission_config"; - static const directKey = "m.direct"; - static const recentEmojiKey = "m.recent_emoji"; + const factory AccountData({ + @JsonKey(name: InvitePermissionConfig.key) + @Default(InvitePermissionConfig()) + InvitePermissionConfig invitePermissionConfig, - Map toJson() => _$AccountDataToJson(this); + @JsonKey(name: "m.direct") + @Default(IMap.empty()) + IMap> directMessages, + + @JsonKey( + name: "m.recent_emoji", + readValue: AccountData.readRecentEmojiValue, + toJson: AccountData.recentEmojiToJson, + ) + @Default(IList.empty()) + IList recentEmoji, + }) = _AccountData; factory AccountData.fromJson(Map json) => _$AccountDataFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RecentEmojiData({ - final IList recentEmoji = const IList.empty(), -}) with _$RecentEmojiData { - Map toJson() => _$RecentEmojiDataToJson(this); +@freezed +abstract class InvitePermissionConfig with _$InvitePermissionConfig { + static const key = "m.invite_permission_config"; - factory RecentEmojiData.fromJson(Map json) => - _$RecentEmojiDataFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const InvitePermissionConfig({ - @JsonKey(unknownEnumValue: DefaultInviteAction.allow) - final DefaultInviteAction defaultAction = DefaultInviteAction.allow, -}) with _$InvitePermissionConfig { - Map toJson() => _$InvitePermissionConfigToJson(this); + const factory InvitePermissionConfig({ + @JsonKey(unknownEnumValue: DefaultInviteAction.allow) + @Default(DefaultInviteAction.allow) + DefaultInviteAction defaultAction, + }) = _InvitePermissionConfig; factory InvitePermissionConfig.fromJson(Map json) => _$InvitePermissionConfigFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RecentEmoji({required final String emoji, required final int total}) - with _$RecentEmoji { - Map toJson() => _$RecentEmojiToJson(this); +@freezed +abstract class RecentEmoji with _$RecentEmoji { + const factory RecentEmoji({required String emoji, required int total}) = + _RecentEmoji; factory RecentEmoji.fromJson(Map json) => _$RecentEmojiFromJson(json); diff --git a/lib/models/capabilities.dart b/lib/models/capabilities.dart deleted file mode 100644 index 6830170..0000000 --- a/lib/models/capabilities.dart +++ /dev/null @@ -1,25 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -part "capabilities.freezed.dart"; -part "capabilities.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Capabilities({ - @JsonKey(name: "org.matrix.msc4174.webpush") final WebPush? webpush, -}) with _$Capabilities { - Map toJson() => _$CapabilitiesToJson(this); - - factory Capabilities.fromJson(Map json) => - _$CapabilitiesFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const WebPush({required final bool enabled, final String? vapid}) - with _$WebPush { - Map toJson() => _$WebPushToJson(this); - - factory WebPush.fromJson(Map json) => - _$WebPushFromJson(json); -} diff --git a/lib/models/client_state.dart b/lib/models/client_state.dart index 262203d..1e15136 100644 --- a/lib/models/client_state.dart +++ b/lib/models/client_state.dart @@ -1,19 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "client_state.freezed.dart"; part "client_state.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ClientState({ - required final bool isInitialized, - required final bool isLoggedIn, - required final bool isVerified, - required final String? userId, - required final String? deviceId, - required final String? homeserverUrl, -}) with _$ClientState { - Map toJson() => _$ClientStateToJson(this); +@freezed +abstract class ClientState with _$ClientState { + const factory ClientState({ + required bool isInitialized, + required bool isLoggedIn, + required bool isVerified, + required String? userId, + required String? homeserverUrl, + }) = _ClientState; factory ClientState.fromJson(Map json) => _$ClientStateFromJson(json); diff --git a/lib/models/configs/members_by_status.dart b/lib/models/configs/members_by_status.dart index 6b4aec4..29fc471 100644 --- a/lib/models/configs/members_by_status.dart +++ b/lib/models/configs/members_by_status.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/membership_status.dart"; - part "members_by_status.freezed.dart"; part "members_by_status.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const MembersByStatusConfig({ - required final String roomId, - required final MembershipStatus status, -}) with _$MembersByStatusConfig { - Map toJson() => _$MembersByStatusConfigToJson(this); +@freezed +abstract class MembersByStatusConfig with _$MembersByStatusConfig { + const factory MembersByStatusConfig({ + required String roomId, + required MembershipStatus status, + }) = _MembersByStatusConfig; factory MembersByStatusConfig.fromJson(Map json) => _$MembersByStatusConfigFromJson(json); diff --git a/lib/models/configs/power_level.dart b/lib/models/configs/power_level.dart index 5722db7..ed9c5e8 100644 --- a/lib/models/configs/power_level.dart +++ b/lib/models/configs/power_level.dart @@ -1,7 +1,6 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/membership_action.dart"; - part "power_level.freezed.dart"; @freezed diff --git a/lib/models/configs/reactions.dart b/lib/models/configs/reactions.dart index b9de793..787b28c 100644 --- a/lib/models/configs/reactions.dart +++ b/lib/models/configs/reactions.dart @@ -1,15 +1,13 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "reactions.freezed.dart"; part "reactions.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ReactionsConfig({ - required final String roomId, - required final int eventRowId, -}) with _$ReactionsConfig { - Map toJson() => _$ReactionsConfigToJson(this); +@freezed +abstract class ReactionsConfig with _$ReactionsConfig { + const factory ReactionsConfig({ + required String roomId, + required int eventRowId, + }) = _ReactionsConfig; factory ReactionsConfig.fromJson(Map json) => _$ReactionsConfigFromJson(json); diff --git a/lib/models/configs/user.dart b/lib/models/configs/user.dart index bbbc83d..0331597 100644 --- a/lib/models/configs/user.dart +++ b/lib/models/configs/user.dart @@ -1,13 +1,11 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "user.freezed.dart"; part "user.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const UserConfig({final String? roomId, required final String userId}) - with _$UserConfig { - Map toJson() => _$UserConfigToJson(this); +@freezed +abstract class UserConfig with _$UserConfig { + const factory UserConfig({required String? roomId, required String userId}) = + _UserConfig; factory UserConfig.fromJson(Map json) => _$UserConfigFromJson(json); diff --git a/lib/models/content/avatar.dart b/lib/models/content/avatar.dart index 25167e9..66d4c47 100644 --- a/lib/models/content/avatar.dart +++ b/lib/models/content/avatar.dart @@ -5,7 +5,7 @@ part "avatar.freezed.dart"; part "avatar.g.dart"; @freezed -sealed class AvatarContent extends Content with _$AvatarContent { +abstract class AvatarContent extends Content with _$AvatarContent { AvatarContent._(); factory AvatarContent({ImageInfo? info, Uri? url}) = _AvatarContent; diff --git a/lib/models/content/canonical_alias.dart b/lib/models/content/canonical_alias.dart index 1a35d92..636be13 100644 --- a/lib/models/content/canonical_alias.dart +++ b/lib/models/content/canonical_alias.dart @@ -5,7 +5,7 @@ part "canonical_alias.freezed.dart"; part "canonical_alias.g.dart"; @freezed -sealed class CanonicalAliasContent extends Content +abstract class CanonicalAliasContent extends Content with _$CanonicalAliasContent { CanonicalAliasContent._(); factory CanonicalAliasContent({ diff --git a/lib/models/content/content.dart b/lib/models/content/content.dart index e28ccbf..e7b1141 100644 --- a/lib/models/content/content.dart +++ b/lib/models/content/content.dart @@ -19,11 +19,9 @@ import "package:nexus/models/content/history_visibility.dart"; class Content { final Error? parseError; + Content({this.parseError}); - const Content({this.parseError}); - - factory Content.fromJson(Map json) => const Content(); - + factory Content.fromJson(Map json) => Content(); Map toJson() => {}; static Map readValue(Map json, _) => @@ -36,16 +34,13 @@ class Content { ?.contentFromJson ?? Content.fromJson)(json); } catch (error) { - if (error is Error) return Content(parseError: error); + if (error is Error) return .new(parseError: error); rethrow; } } } -enum EventType( - final String type, - final Content Function(Map json) contentFromJson, -) { +enum EventType { encrypted("m.room.encrypted", EncryptedContent.fromJson), redaction("m.room.redaction", RedactionContent.fromJson), encryption("m.room.encryption", EncryptionContent.fromJson), @@ -66,4 +61,8 @@ enum EventType( reaction("m.reaction", ReactionContent.fromJson), pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson), message("m.room.message", MessageContent.fromJson); + + final String type; + final Content Function(Map json) contentFromJson; + const EventType(this.type, this.contentFromJson); } diff --git a/lib/models/content/create.dart b/lib/models/content/create.dart index b937a94..c534558 100644 --- a/lib/models/content/create.dart +++ b/lib/models/content/create.dart @@ -5,7 +5,7 @@ part "create.freezed.dart"; part "create.g.dart"; @freezed -sealed class CreateContent extends Content with _$CreateContent { +abstract class CreateContent extends Content with _$CreateContent { CreateContent._(); factory CreateContent({ @JsonKey(name: "additional_creators") @@ -30,10 +30,9 @@ enum RoomType { space, } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const PreviousRoom({required final String roomId}) with _$PreviousRoom { - Map toJson() => _$PreviousRoomToJson(this); +@freezed +abstract class PreviousRoom with _$PreviousRoom { + const factory PreviousRoom({required String roomId}) = _PreviousRoom; factory PreviousRoom.fromJson(Map json) => _$PreviousRoomFromJson(json); diff --git a/lib/models/content/encrypted.dart b/lib/models/content/encrypted.dart index 8658a71..b33a440 100644 --- a/lib/models/content/encrypted.dart +++ b/lib/models/content/encrypted.dart @@ -4,7 +4,7 @@ part "encrypted.freezed.dart"; part "encrypted.g.dart"; @freezed -sealed class EncryptedContent extends Content with _$EncryptedContent { +abstract class EncryptedContent extends Content with _$EncryptedContent { EncryptedContent._(); factory EncryptedContent() = _EncryptedContent; diff --git a/lib/models/content/encryption.dart b/lib/models/content/encryption.dart index 52c7b34..3380632 100644 --- a/lib/models/content/encryption.dart +++ b/lib/models/content/encryption.dart @@ -4,7 +4,7 @@ part "encryption.freezed.dart"; part "encryption.g.dart"; @freezed -sealed class EncryptionContent extends Content with _$EncryptionContent { +abstract class EncryptionContent extends Content with _$EncryptionContent { EncryptionContent._(); factory EncryptionContent({ required String algorithm, diff --git a/lib/models/content/history_visibility.dart b/lib/models/content/history_visibility.dart index 3cfc011..707805c 100644 --- a/lib/models/content/history_visibility.dart +++ b/lib/models/content/history_visibility.dart @@ -4,7 +4,7 @@ part "history_visibility.freezed.dart"; part "history_visibility.g.dart"; @freezed -sealed class HistoryVisibilityContent extends Content +abstract class HistoryVisibilityContent extends Content with _$HistoryVisibilityContent { HistoryVisibilityContent._(); factory HistoryVisibilityContent({ diff --git a/lib/models/content/join_rules.dart b/lib/models/content/join_rules.dart index 3f1c17b..1d14eee 100644 --- a/lib/models/content/join_rules.dart +++ b/lib/models/content/join_rules.dart @@ -6,7 +6,7 @@ part "join_rules.freezed.dart"; part "join_rules.g.dart"; @freezed -sealed class JoinRulesContent extends Content with _$JoinRulesContent { +abstract class JoinRulesContent extends Content with _$JoinRulesContent { JoinRulesContent._(); factory JoinRulesContent({ required JoinRule joinRule, @@ -17,13 +17,12 @@ sealed class JoinRulesContent extends Content with _$JoinRulesContent { _$JoinRulesContentFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const AllowCondition({ - final String? roomId, - required final AllowConditionType type, -}) with _$AllowCondition { - Map toJson() => _$AllowConditionToJson(this); +@freezed +abstract class AllowCondition with _$AllowCondition { + const factory AllowCondition({ + String? roomId, + required AllowConditionType type, + }) = _AllowCondition; factory AllowCondition.fromJson(Map json) => _$AllowConditionFromJson(json); diff --git a/lib/models/content/membership.dart b/lib/models/content/membership.dart index 46be974..dbbd123 100644 --- a/lib/models/content/membership.dart +++ b/lib/models/content/membership.dart @@ -5,7 +5,7 @@ part "membership.freezed.dart"; part "membership.g.dart"; @freezed -sealed class MembershipContent extends Content with _$MembershipContent { +abstract class MembershipContent extends Content with _$MembershipContent { MembershipContent._(); static String? displaynameFromJson(String? displayName) => diff --git a/lib/models/content/message.dart b/lib/models/content/message.dart index 36bd725..e7e8923 100644 --- a/lib/models/content/message.dart +++ b/lib/models/content/message.dart @@ -10,7 +10,7 @@ part "message.g.dart"; typedef EncryptedFile = Map; @Freezed(unionKey: "msgtype", fallbackUnion: "default") -sealed class MessageContent extends Content with _$MessageContent { +abstract class MessageContent extends Content with _$MessageContent { MessageContent._(); static String? mediaUrlFromJson(Map json, String key) => json[key] ?? json["file"]?[key]; diff --git a/lib/models/content/name.dart b/lib/models/content/name.dart index c451d64..205f6bb 100644 --- a/lib/models/content/name.dart +++ b/lib/models/content/name.dart @@ -4,7 +4,7 @@ part "name.freezed.dart"; part "name.g.dart"; @freezed -sealed class NameContent extends Content with _$NameContent { +abstract class NameContent extends Content with _$NameContent { NameContent._(); factory NameContent({required String name}) = _NameContent; diff --git a/lib/models/content/pinned_events.dart b/lib/models/content/pinned_events.dart index 9dac5de..8aea838 100644 --- a/lib/models/content/pinned_events.dart +++ b/lib/models/content/pinned_events.dart @@ -5,7 +5,7 @@ part "pinned_events.freezed.dart"; part "pinned_events.g.dart"; @freezed -sealed class PinnedEventsContent extends Content with _$PinnedEventsContent { +abstract class PinnedEventsContent extends Content with _$PinnedEventsContent { PinnedEventsContent._(); factory PinnedEventsContent({ @Default(IList.empty()) @JsonKey(name: "pinned") IList pinnedEvents, diff --git a/lib/models/content/power_levels.dart b/lib/models/content/power_levels.dart index 5ae6561..3709c38 100644 --- a/lib/models/content/power_levels.dart +++ b/lib/models/content/power_levels.dart @@ -5,7 +5,7 @@ part "power_levels.freezed.dart"; part "power_levels.g.dart"; @freezed -sealed class PowerLevelsContent extends Content with _$PowerLevelsContent { +abstract class PowerLevelsContent extends Content with _$PowerLevelsContent { PowerLevelsContent._(); factory PowerLevelsContent({ @Default(IMap.empty()) IMap events, @@ -24,13 +24,12 @@ sealed class PowerLevelsContent extends Content with _$PowerLevelsContent { _$PowerLevelsContentFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Notifications({ - final int room = 50, - final IMap other = const IMap.empty(), -}) with _$Notifications { - Map toJson() => _$NotificationsToJson(this); +@freezed +abstract class Notifications with _$Notifications { + const factory Notifications({ + @Default(50) int room, + @Default(IMapConst({})) IMap other, + }) = _Notifications; factory Notifications.fromJson(Map json) => _$NotificationsFromJson(json); diff --git a/lib/models/content/reaction.dart b/lib/models/content/reaction.dart index 93c563d..0f81bc0 100644 --- a/lib/models/content/reaction.dart +++ b/lib/models/content/reaction.dart @@ -4,7 +4,7 @@ part "reaction.freezed.dart"; part "reaction.g.dart"; @Freezed(toJson: false) -sealed class ReactionContent extends Content with _$ReactionContent { +abstract class ReactionContent extends Content with _$ReactionContent { ReactionContent._(); static String? keyJsonFromJson(Map json, String key) => json["m.relates_to"]?["key"]; diff --git a/lib/models/content/redaction.dart b/lib/models/content/redaction.dart index 2ed6436..e9c1a90 100644 --- a/lib/models/content/redaction.dart +++ b/lib/models/content/redaction.dart @@ -4,7 +4,7 @@ part "redaction.freezed.dart"; part "redaction.g.dart"; @freezed -sealed class RedactionContent extends Content with _$RedactionContent { +abstract class RedactionContent extends Content with _$RedactionContent { RedactionContent._(); factory RedactionContent({String? reason, String? redacts}) = _RedactionContent; diff --git a/lib/models/content/server_acl.dart b/lib/models/content/server_acl.dart index 3873bf3..1e50988 100644 --- a/lib/models/content/server_acl.dart +++ b/lib/models/content/server_acl.dart @@ -5,7 +5,7 @@ part "server_acl.freezed.dart"; part "server_acl.g.dart"; @freezed -sealed class ServerACLContent extends Content with _$ServerACLContent { +abstract class ServerACLContent extends Content with _$ServerACLContent { ServerACLContent._(); factory ServerACLContent({ @Default(IList.empty()) IList allow, diff --git a/lib/models/content/sticker.dart b/lib/models/content/sticker.dart index c6fa893..89d9332 100644 --- a/lib/models/content/sticker.dart +++ b/lib/models/content/sticker.dart @@ -5,7 +5,7 @@ part "sticker.freezed.dart"; part "sticker.g.dart"; @freezed -sealed class StickerContent extends Content with _$StickerContent { +abstract class StickerContent extends Content with _$StickerContent { StickerContent._(); factory StickerContent({ required String body, diff --git a/lib/models/content/topic.dart b/lib/models/content/topic.dart index c8256e3..8fa5229 100644 --- a/lib/models/content/topic.dart +++ b/lib/models/content/topic.dart @@ -1,12 +1,11 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; - part "topic.freezed.dart"; part "topic.g.dart"; @freezed -sealed class TopicContent extends Content with _$TopicContent { +abstract class TopicContent extends Content with _$TopicContent { TopicContent._(); factory TopicContent({ required String topic, @@ -17,24 +16,24 @@ sealed class TopicContent extends Content with _$TopicContent { _$TopicContentFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const TopicContentBlock({ - final IList representations = const IList.empty(), -}) with _$TopicContentBlock { - Map toJson() => _$TopicContentBlockToJson(this); +@freezed +abstract class TopicContentBlock with _$TopicContentBlock { + factory TopicContentBlock({ + @Default(IList.empty()) + @JsonKey(name: "m.text") + IList representations, + }) = _TopicContentBlock; factory TopicContentBlock.fromJson(Map json) => _$TopicContentBlockFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const TextualRepresentation({ - required final String body, - final String mimetype = "text/plain", -}) with _$TextualRepresentation { - Map toJson() => _$TextualRepresentationToJson(this); +@freezed +abstract class TextualRepresentation with _$TextualRepresentation { + factory TextualRepresentation({ + required String body, + @Default("text/plain") String mimetype, + }) = _TextualRepresentation; factory TextualRepresentation.fromJson(Map json) => _$TextualRepresentationFromJson(json); diff --git a/lib/models/direction.dart b/lib/models/direction.dart deleted file mode 100644 index 8c4947e..0000000 --- a/lib/models/direction.dart +++ /dev/null @@ -1,8 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -enum Direction { - @JsonValue("f") - forward, - @JsonValue("b") - backward, -} diff --git a/lib/models/emoji.dart b/lib/models/emoji.dart index 75faf08..8e4eac6 100644 --- a/lib/models/emoji.dart +++ b/lib/models/emoji.dart @@ -1,25 +1,17 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; -import "package:flutter/widgets.dart"; part "emoji.freezed.dart"; part "emoji.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable(createToJson: false) -class Emoji({ - @JsonKey( - fromJson: Emoji.widgetFromJson, - readValue: Emoji.readWidgetValueFromJson, - ) - required final Widget widget, - @JsonKey(name: "emoji") required final String value, - required final IList aliases, - required final String description, - required final IList tags, -}) with _$Emoji { - static Widget widgetFromJson(String emoji) => Text(emoji); +@freezed +abstract class Emoji with _$Emoji { + const factory Emoji({ + required String emoji, + required String category, + required IList aliases, + required String description, + required IList tags, + }) = _Emoji; - static String readWidgetValueFromJson(Map json, _) => - json["emoji"]; factory Emoji.fromJson(Map json) => _$EmojiFromJson(json); } diff --git a/lib/models/emoji_category.dart b/lib/models/emoji_category.dart deleted file mode 100644 index cc39bc3..0000000 --- a/lib/models/emoji_category.dart +++ /dev/null @@ -1,12 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/emoji.dart"; -import "package:flutter/widgets.dart"; -part "emoji_category.freezed.dart"; - -@Freezed(toJson: false, fromJson: false) -class EmojiCategory({ - required final Widget icon, - required final String name, - required final IList emojis, -}) with _$EmojiCategory; diff --git a/lib/models/event.dart b/lib/models/event.dart index dc3dd46..28bf0ae 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -3,39 +3,11 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/epoch_date_time_converter.dart"; import "package:nexus/models/profile_response.dart"; - part "event.freezed.dart"; part "event.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Event({ - @JsonKey(name: "rowid") required final int rowId, - @JsonKey(name: "timeline_rowid") required final int timelineRowId, - final String? stateKey, - required final String roomId, - required final String eventId, - required final String sender, - @JsonKey(readValue: Event.typeJsonFromJson) required final String type, - @EpochDateTimeConverter() required final DateTime timestamp, - final IMap unsigned = const IMap.empty(), - final LocalContent? localContent, - final String? transactionId, - final String? redactedBy, - final String? relatesTo, - final String? relationType, - final String? replyTo, - final String? decryptionError, - final String? sendError, - final IMap reactions = const IMap.empty(), - @JsonKey(name: "last_edit_rowid") final int lastEditRowId = 0, - final UnreadType? unreadType, - final Profile? pmp, - required final Content content, - required final Content? previousContent, -}) with _$Event { - Map toJson() => _$EventToJson(this); - +@freezed +abstract class Event with _$Event { static String typeJsonFromJson(Map json, _) => json["decrypted_type"] ?? json["type"]; @@ -53,6 +25,32 @@ class const Event({ } } + const factory Event({ + @JsonKey(name: "rowid") required int rowId, + @JsonKey(name: "timeline_rowid") required int timelineRowId, + required String roomId, + required String eventId, + required String sender, + @JsonKey(readValue: Event.typeJsonFromJson) required String type, + String? stateKey, + @EpochDateTimeConverter() required DateTime timestamp, + @Default(IMap.empty()) IMap unsigned, + LocalContent? localContent, + String? transactionId, + String? redactedBy, + String? relatesTo, + String? relationType, + String? replyTo, + String? decryptionError, + String? sendError, + @Default(IMap.empty()) IMap reactions, + @JsonKey(name: "last_edit_rowid") @Default(0) int lastEditRowId, + @UnreadTypeConverter() UnreadType? unreadType, + Profile? pmp, + required Content content, + required Content? previousContent, + }) = _Event; + factory Event.fromJson(Map json) => _$EventFromJson(json).copyWith( replyTo: replyToFromJson(getContentFromJson(json)), @@ -74,27 +72,37 @@ class const Event({ ); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const LocalContent({ - final String? sanitizedHtml, - final String? editSource, - final bool? wasPlaintext, - final bool? bigEmoji, - final bool? hasMath, - final bool? replyFallbackRemoved, -}) with _$LocalContent { - Map toJson() => _$LocalContentToJson(this); +@freezed +abstract class LocalContent with _$LocalContent { + const factory LocalContent({ + String? sanitizedHtml, + String? editSource, + bool? wasPlaintext, + bool? bigEmoji, + bool? hasMath, + bool? replyFallbackRemoved, + }) = _LocalContent; - @override factory LocalContent.fromJson(Map json) => _$LocalContentFromJson(json); } -@Freezed(toJson: false, fromJson: false) -class const UnreadType(final int value) with _$UnreadType { - factory UnreadType.fromJson(int json) => UnreadType(json); - int toJson() => value; +class UnreadTypeConverter implements JsonConverter { + const UnreadTypeConverter(); + + @override + UnreadType? fromJson(int? json) => json == null ? null : UnreadType(json); + + @override + int? toJson(UnreadType? object) => object?.value; +} + +// I think this is correct but I'm not sure, its some type of bitmask. +@immutable +class UnreadType { + final int value; + + const UnreadType(this.value); static const none = UnreadType(0); static const normal = UnreadType(1); @@ -102,9 +110,9 @@ class const UnreadType(final int value) with _$UnreadType { static const highlight = UnreadType(4); static const sound = UnreadType(8); - bool isNone() => value == 0; - bool isNormal() => (value & 1) != 0; - bool shouldNotify() => (value & 2) != 0; - bool isHighlighted() => (value & 4) != 0; - bool playsSound() => (value & 8) != 0; + bool get isNone => value == 0; + bool get isNormal => (value & 1) != 0; + bool get shouldNotify => (value & 2) != 0; + bool get isHighlighted => (value & 4) != 0; + bool get playsSound => (value & 8) != 0; } diff --git a/lib/models/event_context.dart b/lib/models/event_context.dart deleted file mode 100644 index 6103c7d..0000000 --- a/lib/models/event_context.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/event.dart"; - -part "event_context.freezed.dart"; -part "event_context.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const EventContext({ - required final Event event, - required final IList before, - required final IList after, - required final String start, - required final String end, - final IList relatedEvents = const IList.empty(), -}) with _$EventContext { - Map toJson() => _$EventContextToJson(this); - - factory EventContext.fromJson(Map json) => - _$EventContextFromJson(json); -} diff --git a/lib/models/gomuks_config.dart b/lib/models/gomuks_config.dart deleted file mode 100644 index e6a8ce7..0000000 --- a/lib/models/gomuks_config.dart +++ /dev/null @@ -1,55 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -part "gomuks_config.freezed.dart"; -part "gomuks_config.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const GomuksConfig({ - final MatrixConfig? matrix, - final PushConfig? push, - final MediaConfig? media, -}) with _$GomuksConfig { - Map toJson() => _$GomuksConfigToJson(this); - - factory GomuksConfig.fromJson(Map json) => - _$GomuksConfigFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const MatrixConfig({ - @JsonKey(name: "disable_http2") final bool disableHttp2 = false, - @JsonKey(name: "set_presence") final String? setPresence, - @JsonKey(name: "initial_device_display_name") - required final String initialDeviceDisplayName, -}) with _$MatrixConfig { - Map toJson() => _$MatrixConfigToJson(this); - - factory MatrixConfig.fromJson(Map json) => - _$MatrixConfigFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const PushConfig({ - @JsonKey(name: "fcm_gateway") required final String fcmGateway, - @JsonKey(name: "vapid_private_key") required final String vapidPrivateKey, - @JsonKey(name: "vapid_public_key") required final String vapidPublicKey, -}) with _$PushConfig { - Map toJson() => _$PushConfigToJson(this); - - factory PushConfig.fromJson(Map json) => - _$PushConfigFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const MediaConfig({ - @JsonKey(name: "thumbnail_size") required final int thumbnailSize, -}) with _$MediaConfig { - Map toJson() => _$MediaConfigToJson(this); - - factory MediaConfig.fromJson(Map json) => - _$MediaConfigFromJson(json); -} diff --git a/lib/models/homeserver.dart b/lib/models/homeserver.dart index 5504941..903e23d 100644 --- a/lib/models/homeserver.dart +++ b/lib/models/homeserver.dart @@ -1,11 +1,12 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "homeserver.freezed.dart"; @freezed -class const Homeserver({ - required final String name, - required final String description, - required final Uri url, - required final String iconUrl, -}) with _$Homeserver; +abstract class Homeserver with _$Homeserver { + const factory Homeserver({ + required String name, + required String description, + required Uri url, + required String iconUrl, + }) = _Homeserver; +} diff --git a/lib/models/info/audio.dart b/lib/models/info/audio.dart index ac90282..ccfcf7a 100644 --- a/lib/models/info/audio.dart +++ b/lib/models/info/audio.dart @@ -1,17 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/ms_duration.dart"; - part "audio.freezed.dart"; part "audio.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const AudioInfo({ - @MSDuration() final Duration? duration, - @JsonKey(name: "mimetype") final String? mimeType, - final int? size, -}) with _$AudioInfo { - Map toJson() => _$AudioInfoToJson(this); +@freezed +abstract class AudioInfo with _$AudioInfo { + /// Information for images, [size] is in bytes. + const factory AudioInfo({ + @MSDuration() Duration? duration, + @JsonKey(name: "mimetype") String? mimeType, + int? size, + }) = _AudioInfo; factory AudioInfo.fromJson(Map json) => _$AudioInfoFromJson(json); diff --git a/lib/models/info/file.dart b/lib/models/info/file.dart index ce0aa84..1509c99 100644 --- a/lib/models/info/file.dart +++ b/lib/models/info/file.dart @@ -1,15 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "file.freezed.dart"; part "file.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const FileInfo({ - @JsonKey(name: "mimetype") final String? mimeType, - final int? size, -}) with _$FileInfo { - Map toJson() => _$FileInfoToJson(this); +@freezed +abstract class FileInfo with _$FileInfo { + /// Information for images, [size] is in bytes. + const factory FileInfo({ + @JsonKey(name: "mimetype") String? mimeType, + int? size, + }) = _FileInfo; factory FileInfo.fromJson(Map json) => _$FileInfoFromJson(json); diff --git a/lib/models/info/image.dart b/lib/models/info/image.dart index 266fb01..9833016 100644 --- a/lib/models/info/image.dart +++ b/lib/models/info/image.dart @@ -1,18 +1,17 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "image.freezed.dart"; part "image.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ImageInfo({ - @JsonKey(name: "h") final double? height, - @JsonKey(name: "w") final double? width, - @JsonKey(name: "mimetype") final String? mimeType, - @JsonKey(name: "xyz.amorgan.blurhash") final String? blurHash, - final int? size, -}) with _$ImageInfo { - Map toJson() => _$ImageInfoToJson(this); +@freezed +abstract class ImageInfo with _$ImageInfo { + /// Information for images, [size] is in bytes. + const factory ImageInfo({ + @JsonKey(name: "h") double? height, + @JsonKey(name: "w") double? width, + @JsonKey(name: "mimetype") String? mimeType, + @JsonKey(name: "xyz.amorgan.blurhash") String? blurHash, + int? size, + }) = _ImageInfo; factory ImageInfo.fromJson(Map json) => _$ImageInfoFromJson(json); diff --git a/lib/models/info/video.dart b/lib/models/info/video.dart index 31b1fb6..6ff3547 100644 --- a/lib/models/info/video.dart +++ b/lib/models/info/video.dart @@ -1,19 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/ms_duration.dart"; - part "video.freezed.dart"; part "video.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const VideoInfo({ - @JsonKey(name: "h") final int? height, - @JsonKey(name: "w") final int? width, - @JsonKey(name: "mimetype") final String? mimeType, - @MSDuration() final Duration? duration, - final int? size, -}) with _$VideoInfo { - Map toJson() => _$VideoInfoToJson(this); +@freezed +abstract class VideoInfo with _$VideoInfo { + /// Information for images, [size] is in bytes. + const factory VideoInfo({ + @JsonKey(name: "h") int? height, + @JsonKey(name: "w") int? width, + @JsonKey(name: "mimetype") String? mimeType, + @MSDuration() Duration? duration, + int? size, + }) = _VideoInfo; factory VideoInfo.fromJson(Map json) => _$VideoInfoFromJson(json); diff --git a/lib/models/join_rule.dart b/lib/models/join_rule.dart index 44fb9bb..3fade23 100644 --- a/lib/models/join_rule.dart +++ b/lib/models/join_rule.dart @@ -1,4 +1,4 @@ import "package:freezed_annotation/freezed_annotation.dart"; -@JsonEnum(fieldRename: .snake) +@JsonEnum(fieldRename: FieldRename.snake) enum JoinRule { public, knock, invite, private, restricted, knockRestricted } diff --git a/lib/models/lazy_load_summary.dart b/lib/models/lazy_load_summary.dart index f9a814e..0cd250f 100644 --- a/lib/models/lazy_load_summary.dart +++ b/lib/models/lazy_load_summary.dart @@ -1,17 +1,15 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "lazy_load_summary.freezed.dart"; part "lazy_load_summary.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const LazyLoadSummary({ - required final IList? heroes, - required final int? joinedMemberCount, - required final int? invitedMemberCount, -}) with _$LazyLoadSummary { - Map toJson() => _$LazyLoadSummaryToJson(this); +@freezed +abstract class LazyLoadSummary with _$LazyLoadSummary { + const factory LazyLoadSummary({ + required IList? heroes, + required int? joinedMemberCount, + required int? invitedMemberCount, + }) = _LazyLoadSummary; factory LazyLoadSummary.fromJson(Map json) => _$LazyLoadSummaryFromJson(json); diff --git a/lib/models/membership_action.dart b/lib/models/membership_action.dart index f03ca99..d852164 100644 --- a/lib/models/membership_action.dart +++ b/lib/models/membership_action.dart @@ -1 +1,4 @@ +import "package:freezed_annotation/freezed_annotation.dart"; + +@JsonEnum() enum MembershipAction { ban, kick, unban, invite } diff --git a/lib/models/membership_status.dart b/lib/models/membership_status.dart index 7eb5f72..ba7a241 100644 --- a/lib/models/membership_status.dart +++ b/lib/models/membership_status.dart @@ -1 +1,4 @@ +import "package:freezed_annotation/freezed_annotation.dart"; + +@JsonEnum() enum MembershipStatus { leave, invite, ban, join, knock } diff --git a/lib/models/oauth_auth_code_response.dart b/lib/models/oauth_auth_code_response.dart index 3074335..5bb3f3f 100644 --- a/lib/models/oauth_auth_code_response.dart +++ b/lib/models/oauth_auth_code_response.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "oauth_auth_code_response.freezed.dart"; part "oauth_auth_code_response.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const OAuthAuthCodeResponse({ - required final String state, - required final String codeVerifier, - required final Uri url, -}) with _$OAuthAuthCodeResponse { - Map toJson() => _$OAuthAuthCodeResponseToJson(this); +@freezed +abstract class OAuthAuthCodeResponse with _$OAuthAuthCodeResponse { + const factory OAuthAuthCodeResponse({ + required String state, + required String codeVerifier, + required Uri url, + }) = _OAuthAuthCodeResponse; factory OAuthAuthCodeResponse.fromJson(Map json) => _$OAuthAuthCodeResponseFromJson(json); diff --git a/lib/models/open_graph_data.dart b/lib/models/open_graph_data.dart index a183ef7..d7e840d 100644 --- a/lib/models/open_graph_data.dart +++ b/lib/models/open_graph_data.dart @@ -1,18 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "open_graph_data.freezed.dart"; part "open_graph_data.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const OpenGraphData({ - @JsonKey(name: "og:title") required final String? title, - @JsonKey(name: "og:description") required final String? description, - @JsonKey(name: "og:image") required final Uri? imageUrl, - @JsonKey(name: "og:image:width") required final double? width, - @JsonKey(name: "og:image:height") required final double? height, -}) with _$OpenGraphData { - Map toJson() => _$OpenGraphDataToJson(this); +@freezed +abstract class OpenGraphData with _$OpenGraphData { + const factory OpenGraphData({ + @JsonKey(name: "og:title") required String? title, + @JsonKey(name: "og:description") required String? description, + @JsonKey(name: "og:image") required Uri? imageUrl, + @JsonKey(name: "og:image:width") required double? width, + @JsonKey(name: "og:image:height") required double? height, + }) = _OpenGraphData; factory OpenGraphData.fromJson(Map json) => _$OpenGraphDataFromJson(json); diff --git a/lib/models/paginate.dart b/lib/models/paginate.dart index dc6eede..df0a0f6 100644 --- a/lib/models/paginate.dart +++ b/lib/models/paginate.dart @@ -1,18 +1,16 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/event.dart"; - part "paginate.freezed.dart"; part "paginate.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Paginate({ - required final IList events, - required final IList relatedEvents, - required final bool hasMore, -}) with _$Paginate { - Map toJson() => _$PaginateToJson(this); +@freezed +abstract class Paginate with _$Paginate { + const factory Paginate({ + required IList events, + required IList relatedEvents, + required bool hasMore, + }) = _Paginate; factory Paginate.fromJson(Map json) => _$PaginateFromJson(json); diff --git a/lib/models/paginate_manual.dart b/lib/models/paginate_manual.dart deleted file mode 100644 index 9f45c1f..0000000 --- a/lib/models/paginate_manual.dart +++ /dev/null @@ -1,19 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/event.dart"; - -part "paginate_manual.freezed.dart"; -part "paginate_manual.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const PaginateManual({ - required final IList events, - final IList relatedEvents = const IList.empty(), - required final String? nextBatch, -}) with _$PaginateManual { - Map toJson() => _$PaginateManualToJson(this); - - factory PaginateManual.fromJson(Map json) => - _$PaginateManualFromJson(json); -} diff --git a/lib/models/profile_response.dart b/lib/models/profile_response.dart index 57f76c5..8b7b749 100644 --- a/lib/models/profile_response.dart +++ b/lib/models/profile_response.dart @@ -1,55 +1,53 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/membership.dart"; - part "profile_response.freezed.dart"; part "profile_response.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ProfileResponse({ - @JsonKey(fromJson: Profile.fromJson) required final Profile profile, - final Bio? bio, -}) with _$ProfileResponse { - Map toJson() => _$ProfileResponseToJson(this); +@freezed +abstract class ProfileResponse with _$ProfileResponse { + const factory ProfileResponse({ + @JsonKey(fromJson: Profile.fromJson) required Profile profile, + required Bio? bio, + }) = _ProfileResponse; factory ProfileResponse.fromJson(Map json) => _$ProfileResponseFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Bio({required final String html, final String? editSource}) - with _$Bio { - Map toJson() => _$BioToJson(this); +@freezed +abstract class Bio with _$Bio { + const factory Bio({required String html, String? editSource}) = _Bio; factory Bio.fromJson(Map json) => _$BioFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Profile({ - final String? id, - final String? parseError, - final Uri? avatarUrl, - - @JsonKey(name: "displayname", fromJson: MembershipContent.displaynameFromJson) - final String? displayName, - - @JsonKey(readValue: Profile.readTimezone, name: "m.tz") - final String? timezone, - - @JsonKey(readValue: Profile.readPronouns, name: "m.pronouns") - final IList pronouns = const IList.empty(), -}) with _$Profile { - Map toJson() => _$ProfileToJson(this); - +@freezed +abstract class Profile with _$Profile { static Object? readPronouns(Map map, String key) => map[key] ?? map["io.fsky.nyx.pronouns"]; static Object? readTimezone(Map map, String key) => map[key] ?? map["us.cloke.msc4175.tz"]; + const factory Profile({ + String? id, + String? parseError, + Uri? avatarUrl, + + @JsonKey( + name: "displayname", + fromJson: MembershipContent.displaynameFromJson, + ) + String? displayName, + + @JsonKey(readValue: Profile.readTimezone, name: "m.tz") String? timezone, + + @Default(IList.empty()) + @JsonKey(readValue: Profile.readPronouns, name: "m.pronouns") + IList pronouns, + }) = _Profile; + factory Profile.fromJson(Map json) => _$ProfileFromJson(json); @@ -62,13 +60,10 @@ class const Profile({ } } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Pronoun({ - required final String language, - required final String summary, -}) with _$Pronoun { - Map toJson() => _$PronounToJson(this); +@freezed +abstract class Pronoun with _$Pronoun { + const factory Pronoun({required String language, required String summary}) = + _Pronoun; factory Pronoun.fromJson(Map json) => _$PronounFromJson(json); diff --git a/lib/models/read_receipt.dart b/lib/models/read_receipt.dart index ab4edd8..d533e2d 100644 --- a/lib/models/read_receipt.dart +++ b/lib/models/read_receipt.dart @@ -1,19 +1,17 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/epoch_date_time_converter.dart"; - part "read_receipt.freezed.dart"; part "read_receipt.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ReadReceipt({ - final String? roomId, - required final String userId, - final String? threadId, - required final String eventId, - @EpochDateTimeConverter() required final DateTime timestamp, -}) with _$ReadReceipt { - Map toJson() => _$ReadReceiptToJson(this); +@freezed +abstract class ReadReceipt with _$ReadReceipt { + const factory ReadReceipt({ + String? roomId, + required String userId, + String? threadId, + required String eventId, + @EpochDateTimeConverter() required DateTime timestamp, + }) = _ReadReceipt; factory ReadReceipt.fromJson(Map json) => _$ReadReceiptFromJson(json); diff --git a/lib/models/requests/deregister_pusher.dart b/lib/models/requests/deregister_pusher.dart deleted file mode 100644 index 1416add..0000000 --- a/lib/models/requests/deregister_pusher.dart +++ /dev/null @@ -1,13 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -part "deregister_pusher.freezed.dart"; -part "deregister_pusher.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const DeregisterPusherRequest({ - required final String appId, - @JsonKey(name: "pushkey") required final String pushKey, -}) with _$DeregisterPusherRequest { - Map toJson() => _$DeregisterPusherRequestToJson(this); -} diff --git a/lib/models/requests/download_media.dart b/lib/models/requests/download_media.dart index c9751f8..b5d5771 100644 --- a/lib/models/requests/download_media.dart +++ b/lib/models/requests/download_media.dart @@ -1,17 +1,15 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "download_media.freezed.dart"; part "download_media.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const DownloadMediaRequest({ - required final Uri mxc, - final bool encrypted = false, - final bool isAvatar = false, - final bool thumbnailAvatar = false, -}) with _$DownloadMediaRequest { - Map toJson() => _$DownloadMediaRequestToJson(this); +@freezed +abstract class DownloadMediaRequest with _$DownloadMediaRequest { + const factory DownloadMediaRequest({ + required Uri mxc, + @Default(false) bool encrypted, + @Default(false) bool isAvatar, + @Default(false) bool thumbnailAvatar, + }) = _DownloadMediaRequest; factory DownloadMediaRequest.fromJson(Map json) => _$DownloadMediaRequestFromJson(json); diff --git a/lib/models/requests/get_event.dart b/lib/models/requests/get_event.dart index b4692ee..2665a2a 100644 --- a/lib/models/requests/get_event.dart +++ b/lib/models/requests/get_event.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "get_event.freezed.dart"; part "get_event.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const GetEventRequest({ - required final String roomId, - required final String eventId, - final bool unredact = false, -}) with _$GetEventRequest { - Map toJson() => _$GetEventRequestToJson(this); +@freezed +abstract class GetEventRequest with _$GetEventRequest { + const factory GetEventRequest({ + required String roomId, + required String eventId, + @Default(false) bool unredact, + }) = _GetEventRequest; factory GetEventRequest.fromJson(Map json) => _$GetEventRequestFromJson(json); diff --git a/lib/models/requests/get_event_context.dart b/lib/models/requests/get_event_context.dart deleted file mode 100644 index 68f23a0..0000000 --- a/lib/models/requests/get_event_context.dart +++ /dev/null @@ -1,17 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -part "get_event_context.freezed.dart"; -part "get_event_context.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const GetEventContextRequest({ - required final String roomId, - required final String eventId, - final int limit = 20, -}) with _$GetEventContextRequest { - Map toJson() => _$GetEventContextRequestToJson(this); - - factory GetEventContextRequest.fromJson(Map json) => - _$GetEventContextRequestFromJson(json); -} diff --git a/lib/models/requests/get_mentions.dart b/lib/models/requests/get_mentions.dart deleted file mode 100644 index 1512b6f..0000000 --- a/lib/models/requests/get_mentions.dart +++ /dev/null @@ -1,19 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/epoch_date_time_converter.dart"; -import "package:nexus/models/event.dart"; -part "get_mentions.freezed.dart"; -part "get_mentions.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class GetMentionsRequest({ - @EpochDateTimeConverter() required final DateTime maxTimestamp, - @JsonKey(name: "type") required final UnreadType unreadType, - final int limit = 20, - final String? roomId, -}) with _$GetMentionsRequest { - Map toJson() => _$GetMentionsRequestToJson(this); - - factory GetMentionsRequest.fromJson(Map json) => - _$GetMentionsRequestFromJson(json); -} diff --git a/lib/models/requests/get_related_events.dart b/lib/models/requests/get_related_events.dart index 08be426..52d2716 100644 --- a/lib/models/requests/get_related_events.dart +++ b/lib/models/requests/get_related_events.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "get_related_events.freezed.dart"; part "get_related_events.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const GetRelatedEventsRequest({ - required final String roomId, - required final String eventId, - required final String relationType, -}) with _$GetRelatedEventsRequest { - Map toJson() => _$GetRelatedEventsRequestToJson(this); +@freezed +abstract class GetRelatedEventsRequest with _$GetRelatedEventsRequest { + const factory GetRelatedEventsRequest({ + required String roomId, + required String eventId, + required String relationType, + }) = _GetRelatedEventsRequest; factory GetRelatedEventsRequest.fromJson(Map json) => _$GetRelatedEventsRequestFromJson(json); diff --git a/lib/models/requests/get_room_state.dart b/lib/models/requests/get_room_state.dart index 66504da..d3f52f7 100644 --- a/lib/models/requests/get_room_state.dart +++ b/lib/models/requests/get_room_state.dart @@ -1,17 +1,15 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "get_room_state.freezed.dart"; part "get_room_state.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const GetRoomStateRequest({ - required final String roomId, - final bool refetch = false, - final bool fetchMembers = false, - final bool includeMembers = false, -}) with _$GetRoomStateRequest { - Map toJson() => _$GetRoomStateRequestToJson(this); +@freezed +abstract class GetRoomStateRequest with _$GetRoomStateRequest { + const factory GetRoomStateRequest({ + required String roomId, + @Default(false) bool refetch, + @Default(false) bool fetchMembers, + @Default(false) bool includeMembers, + }) = _GetRoomStateRequest; factory GetRoomStateRequest.fromJson(Map json) => _$GetRoomStateRequestFromJson(json); diff --git a/lib/models/requests/join_room.dart b/lib/models/requests/join_room.dart index 350b762..8487298 100644 --- a/lib/models/requests/join_room.dart +++ b/lib/models/requests/join_room.dart @@ -1,16 +1,14 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "join_room.freezed.dart"; part "join_room.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const JoinRoomRequest({ - required final String roomIdOrAlias, - final IList via = const IList.empty(), -}) with _$JoinRoomRequest { - Map toJson() => _$JoinRoomRequestToJson(this); +@freezed +abstract class JoinRoomRequest with _$JoinRoomRequest { + const factory JoinRoomRequest({ + required String roomIdOrAlias, + required IList via, + }) = _JoinRoomRequest; factory JoinRoomRequest.fromJson(Map json) => _$JoinRoomRequestFromJson(json); diff --git a/lib/models/requests/oauth/exchange_token.dart b/lib/models/requests/oauth/exchange_token.dart index fb47358..9d55c1f 100644 --- a/lib/models/requests/oauth/exchange_token.dart +++ b/lib/models/requests/oauth/exchange_token.dart @@ -1,18 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "exchange_token.freezed.dart"; part "exchange_token.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const OAuthExchangeTokenRequest({ - required final Uri homeserverUrl, - required final String codeVerifier, - required final Uri redirectUri, - required final String code, - required final String clientId, -}) with _$OAuthExchangeTokenRequest { - Map toJson() => _$OAuthExchangeTokenRequestToJson(this); +@freezed +abstract class OAuthExchangeTokenRequest with _$OAuthExchangeTokenRequest { + const factory OAuthExchangeTokenRequest({ + required Uri homeserverUrl, + required String codeVerifier, + required Uri redirectUri, + required String code, + required String clientId, + }) = _OAuthExchangeTokenRequest; factory OAuthExchangeTokenRequest.fromJson(Map json) => _$OAuthExchangeTokenRequestFromJson(json); diff --git a/lib/models/requests/oauth/get_auth_url.dart b/lib/models/requests/oauth/get_auth_url.dart index 1ff166a..5ca5b6f 100644 --- a/lib/models/requests/oauth/get_auth_url.dart +++ b/lib/models/requests/oauth/get_auth_url.dart @@ -2,27 +2,25 @@ import "dart:math"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "get_auth_url.freezed.dart"; part "get_auth_url.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const OAuthGetAuthUrl({ - required final ResponseMode responseMode, - required final Uri homeserverUrl, - required final Uri redirectUri, - required final IList scopes, - required final String clientId, - final String? userIdHint, -}) with _$OAuthGetAuthUrl { - Map toJson() => _$OAuthGetAuthUrlToJson(this); +@freezed +abstract class OAuthGetAuthUrl with _$OAuthGetAuthUrl { + const factory OAuthGetAuthUrl({ + required ResponseMode responseMode, + required Uri homeserverUrl, + required Uri redirectUri, + required IList scopes, + required String clientId, + String? userIdHint, + }) = _OAuthGetAuthUrl; factory OAuthGetAuthUrl.fromJson(Map json) => _$OAuthGetAuthUrlFromJson(json); } -sealed class Scope { +abstract class Scope { static final openid = "openid"; static final email = "email"; static final clientApi = "urn:matrix:client:api:*"; diff --git a/lib/models/requests/oauth/register_client.dart b/lib/models/requests/oauth/register_client.dart index e5c1a70..cf4f9e0 100644 --- a/lib/models/requests/oauth/register_client.dart +++ b/lib/models/requests/oauth/register_client.dart @@ -1,27 +1,26 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "register_client.freezed.dart"; part "register_client.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const OAuthRegisterClientRequest({ - required final Uri homeserverUrl, - final ApplicationType applicationType = ApplicationType.web, - final String? clientName, - required final Uri clientUri, - final Uri? logoUri, - final Uri? policyUri, - final Uri? tosUri, - final IList? grantTypes, - final IList? redirectUris, - final IList? responseTypes, +@freezed +abstract class OAuthRegisterClientRequest with _$OAuthRegisterClientRequest { + const factory OAuthRegisterClientRequest({ + required Uri homeserverUrl, + @Default(ApplicationType.web) ApplicationType applicationType, + String? clientName, + required Uri clientUri, + Uri? logoUri, + Uri? policyUri, + Uri? tosUri, + IList? grantTypes, + IList? redirectUris, + IList? responseTypes, - @JsonKey(name: "token_endpoint_auth_method") - final AuthMethod? authMethod = AuthMethod.none, -}) with _$OAuthRegisterClientRequest { - Map toJson() => _$OAuthRegisterClientRequestToJson(this); + @Default(AuthMethod.none) + @JsonKey(name: "token_endpoint_auth_method") + AuthMethod? authMethod, + }) = _OAuthRegisterClientRequest; factory OAuthRegisterClientRequest.fromJson(Map json) => _$OAuthRegisterClientRequestFromJson(json); diff --git a/lib/models/requests/paginate.dart b/lib/models/requests/paginate.dart index b3903c6..ddc62f3 100644 --- a/lib/models/requests/paginate.dart +++ b/lib/models/requests/paginate.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "paginate.freezed.dart"; part "paginate.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const PaginateRequest({ - required final String roomId, - required final int? maxTimelineId, - final int limit = 20, -}) with _$PaginateRequest { - Map toJson() => _$PaginateRequestToJson(this); +@freezed +abstract class PaginateRequest with _$PaginateRequest { + const factory PaginateRequest({ + required String roomId, + required int? maxTimelineId, + @Default(20) int limit, + }) = _PaginateRequest; factory PaginateRequest.fromJson(Map json) => _$PaginateRequestFromJson(json); diff --git a/lib/models/requests/paginate_manual.dart b/lib/models/requests/paginate_manual.dart deleted file mode 100644 index 9f8a1d3..0000000 --- a/lib/models/requests/paginate_manual.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/direction.dart"; - -part "paginate_manual.freezed.dart"; -part "paginate_manual.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const PaginateManualRequest({ - required final String roomId, - // Root event ID of a thread to paginate - final String? threadRoot, - // Can be null for starting pagination of a thread - final String? since, - required final Direction direction, - final int limit = 20, -}) with _$PaginateManualRequest { - Map toJson() => _$PaginateManualRequestToJson(this); - - factory PaginateManualRequest.fromJson(Map json) => - _$PaginateManualRequestFromJson(json); -} diff --git a/lib/models/requests/register_pusher.dart b/lib/models/requests/register_pusher.dart deleted file mode 100644 index dd34e62..0000000 --- a/lib/models/requests/register_pusher.dart +++ /dev/null @@ -1,56 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; - -part "register_pusher.freezed.dart"; -part "register_pusher.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RegisterPusherRequest({ - required final String appDisplayName, - required final String appId, - final bool append = false, - required final PusherData data, - required final String deviceDisplayName, - required final PusherKind kind, - required final String lang, - - /// TODO: What does this do? - final String? profileTag, - - @JsonKey(name: "pushkey") required final String pushKey, -}) with _$RegisterPusherRequest { - Map toJson() => _$RegisterPusherRequestToJson(this); -} - -@freezed -sealed class PusherData with _$PusherData { - const factory PusherData.http({ - required Uri url, - @Default(PushFormat.eventIdOnly) PushFormat format, - }) = HttpPusherData; - - const factory PusherData.webPush({ - required Uri url, - @Default(PushFormat.eventIdOnly) PushFormat format, - - /// `data.auth`: RFC8291 authentication secret. - required String auth, - }) = WebPushPusherData; - - factory PusherData.fromJson(Map json) => - _$PusherDataFromJson(json); -} - -@JsonEnum(fieldRename: .snake) -enum PushFormat { - @JsonValue(null) - all, - eventIdOnly, -} - -enum PusherKind { - http, - email, - @JsonValue("org.matrix.msc4174.webpush") - webPush, -} diff --git a/lib/models/requests/report.dart b/lib/models/requests/report.dart index ed05715..f87b1f1 100644 --- a/lib/models/requests/report.dart +++ b/lib/models/requests/report.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "report.freezed.dart"; part "report.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const ReportRequest({ - required final String roomId, - required final String eventId, - final String? reason, -}) with _$ReportRequest { - Map toJson() => _$ReportRequestToJson(this); +@freezed +abstract class ReportRequest with _$ReportRequest { + const factory ReportRequest({ + required String roomId, + required String eventId, + String? reason, + }) = _ReportRequest; factory ReportRequest.fromJson(Map json) => _$ReportRequestFromJson(json); diff --git a/lib/models/requests/send_event.dart b/lib/models/requests/send_event.dart index acad57f..196c0b5 100644 --- a/lib/models/requests/send_event.dart +++ b/lib/models/requests/send_event.dart @@ -1,21 +1,19 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; - part "send_event.freezed.dart"; part "send_event.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SendEventRequest({ - required final String roomId, - required final String type, - required final Content content, - final String? relatesTo, - final String? relationType, - final bool synchronous = false, - final bool disableEncryption = false, -}) with _$SendEventRequest { - Map toJson() => _$SendEventRequestToJson(this); +@freezed +abstract class SendEventRequest with _$SendEventRequest { + const factory SendEventRequest({ + required String roomId, + required String type, + required Content content, + String? relatesTo, + String? relationType, + @Default(false) bool synchronous, + @Default(false) bool disableEncryption, + }) = _SendEventRequest; factory SendEventRequest.fromJson(Map json) => _$SendEventRequestFromJson(json); diff --git a/lib/models/requests/send_message.dart b/lib/models/requests/send_message.dart index 9907439..951198e 100644 --- a/lib/models/requests/send_message.dart +++ b/lib/models/requests/send_message.dart @@ -2,43 +2,43 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/relation_type.dart"; - part "send_message.freezed.dart"; part "send_message.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SendMessageRequest({ - required final String roomId, - required final String text, - final Content? baseContent, - @JsonKey(name: "mentions") final Mentions mentions = const Mentions(), - @JsonKey(name: "relates_to") final Relation? relation, -}) with _$SendMessageRequest { - Map toJson() => _$SendMessageRequestToJson(this); +@freezed +abstract class SendMessageRequest with _$SendMessageRequest { + const factory SendMessageRequest({ + required String roomId, + required String text, + Content? baseContent, + @Default(Mentions()) @JsonKey(name: "mentions") Mentions mentions, + @JsonKey(name: "relates_to") Relation? relation, + }) = _SendMessageRequest; factory SendMessageRequest.fromJson(Map json) => _$SendMessageRequestFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Mentions({ - final bool room = false, - final IList userIds = const IList.empty(), -}) with _$Mentions { - Map toJson() => _$MentionsToJson(this); +@freezed +abstract class Mentions with _$Mentions { + const factory Mentions({ + @Default(false) bool room, + @Default(IList.empty()) IList userIds, + }) = _Mentions; factory Mentions.fromJson(Map json) => _$MentionsFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable(createToJson: false) -class const Relation({ - required final String eventId, - required final RelationType relationType, -}) with _$Relation { +@Freezed(toJson: false) +abstract class Relation with _$Relation { + const Relation._(); + + const factory Relation({ + required String eventId, + required RelationType relationType, + }) = _Relation; + Map toJson() { switch (relationType) { case RelationType.reply: diff --git a/lib/models/requests/set_account_data.dart b/lib/models/requests/set_account_data.dart index 95ef75c..ffccdb4 100644 --- a/lib/models/requests/set_account_data.dart +++ b/lib/models/requests/set_account_data.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "set_account_data.freezed.dart"; part "set_account_data.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SetAccountDataRequest({ - required final String type, - required final dynamic content, - final String? roomId, -}) with _$SetAccountDataRequest { - Map toJson() => _$SetAccountDataRequestToJson(this); +@freezed +abstract class SetAccountDataRequest with _$SetAccountDataRequest { + const factory SetAccountDataRequest({ + required String type, + required dynamic content, + String? roomId, + }) = _SetAccountDataRequest; factory SetAccountDataRequest.fromJson(Map json) => _$SetAccountDataRequestFromJson(json); diff --git a/lib/models/requests/set_membership.dart b/lib/models/requests/set_membership.dart index 9d88da2..4bbe8a3 100644 --- a/lib/models/requests/set_membership.dart +++ b/lib/models/requests/set_membership.dart @@ -1,19 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/membership_action.dart"; - part "set_membership.freezed.dart"; part "set_membership.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SetMembershipRequest({ - required final String userId, - required final String roomId, - final String? reason, - @JsonKey(name: "action") required final MembershipAction action, - @JsonKey(name: "msc4293_redact_events") final bool redact = false, -}) with _$SetMembershipRequest { - Map toJson() => _$SetMembershipRequestToJson(this); +@freezed +abstract class SetMembershipRequest with _$SetMembershipRequest { + const factory SetMembershipRequest({ + required String userId, + required String roomId, + + String? reason, + @JsonKey(name: "action") required MembershipAction action, + @Default(false) @JsonKey(name: "msc4293_redact_events") bool redact, + }) = _SetMembershipRequest; factory SetMembershipRequest.fromJson(Map json) => _$SetMembershipRequestFromJson(json); diff --git a/lib/models/requests/set_state.dart b/lib/models/requests/set_state.dart index d0e3e95..c92763d 100644 --- a/lib/models/requests/set_state.dart +++ b/lib/models/requests/set_state.dart @@ -1,23 +1,22 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/ms_duration.dart"; - part "set_state.freezed.dart"; part "set_state.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SetStateRequest({ - required final String roomId, - required final String type, - required final String stateKey, - required final Content content, +@freezed +abstract class SetStateRequest with _$SetStateRequest { + const factory SetStateRequest({ + required String roomId, + required String type, + required String stateKey, + required Content content, - @JsonKey(name: "delay_ms", includeIfNull: false) - @MSDuration() - final Duration? delay, -}) with _$SetStateRequest { - Map toJson() => _$SetStateRequestToJson(this); + @JsonKey(name: "delay_ms", includeIfNull: false) + @MSDuration() + @Default(null) + Duration? delay, + }) = _SetStateRequest; factory SetStateRequest.fromJson(Map json) => _$SetStateRequestFromJson(json); diff --git a/lib/models/requests/upload_media.dart b/lib/models/requests/upload_media.dart index d6e69ac..a640bea 100644 --- a/lib/models/requests/upload_media.dart +++ b/lib/models/requests/upload_media.dart @@ -1,26 +1,23 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "upload_media.freezed.dart"; part "upload_media.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const UploadMediaRequest({ - required final String path, - required final bool encrypt, - final String? filename, - @JsonKey(name: "voice_message") final bool isVoiceMessage = false, - final bool forceFile = false, - final - // Below params only work if encodeTo is set - String? - encodeTo, - final int? resizeWidth, - final int? resizeHeight, - final int? resizePercent, - final int quality = 80, -}) with _$UploadMediaRequest { - Map toJson() => _$UploadMediaRequestToJson(this); +@freezed +abstract class UploadMediaRequest with _$UploadMediaRequest { + const factory UploadMediaRequest({ + required String path, + required bool encrypt, + String? filename, + @Default(false) @JsonKey(name: "voice_message") bool isVoiceMessage, + @Default(false) bool forceFile, + + // Below params only work if encodeTo is set + String? encodeTo, + int? resizeWidth, + int? resizeHeight, + int? resizePercent, + @Default(80) int quality, + }) = _UploadMediaRequest; factory UploadMediaRequest.fromJson(Map json) => _$UploadMediaRequestFromJson(json); diff --git a/lib/models/room.dart b/lib/models/room.dart index 96b29b4..fb21a55 100644 --- a/lib/models/room.dart +++ b/lib/models/room.dart @@ -3,44 +3,11 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/read_receipt.dart"; import "package:nexus/models/room_metadata.dart"; - part "room.freezed.dart"; part "room.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Room({ - @JsonKey(name: "meta") final RoomMetadata? metadata, - - /// [timeline] is an IMap of timelineRowId to eventRowId - @JsonKey(fromJson: Room.timelineTupleJsonToIMap) - final IMap timeline = const IMap.empty(), - - /// [clientSticky] is an ISet of eventRowId - @JsonKey(includeFromJson: false, includeToJson: false) - final ISet clientSticky = const ISet.empty(), - - /// [events] is an IMap of eventRowId to event - @JsonKey(fromJson: Room.eventsJsonToIMap) - final IMap events = const IMap.empty(), - - final bool reset = false, - - @JsonKey(includeFromJson: false, includeToJson: false) - final bool hasFetchedState = false, - - @JsonKey(includeFromJson: false, includeToJson: false) - final bool hasFetchedMembers = false, - - final IMap> state = const IMap.empty(), - - final IMap> receipts = const IMap.empty(), - final bool dismissNotifications = false, - final bool hasMore = true, - - // IMap accountData, - // IList notifications, -}) with _$Room { +@freezed +abstract class Room with _$Room { static IMap timelineTupleJsonToIMap(List json) => IMap.fromEntries( json.map( @@ -59,7 +26,32 @@ class const Room({ }), ); - Map toJson() => _$RoomToJson(this); + /// [timeline] is an IMap of timelineRowId to eventRowId + /// [events] is an IMap of eventRowId to event + /// [sticky] is an ISet of eventRowId + const factory Room({ + @JsonKey(name: "meta") RoomMetadata? metadata, + @Default(IMap.empty()) + @JsonKey(fromJson: Room.timelineTupleJsonToIMap) + IMap timeline, + @Default(ISet.empty()) ISet sticky, + + @Default(IMap.empty()) + @JsonKey(fromJson: Room.eventsJsonToIMap) + IMap events, + + @Default(false) bool reset, + @Default(false) bool hasFetchedState, + @Default(false) bool hasFetchedMembers, + @Default(IMap.empty()) IMap> state, + + @Default(IMap.empty()) IMap> receipts, + @Default(false) bool dismissNotifications, + @Default(true) bool hasMore, + + // required IMap accountData, + // required IList notifications, + }) = _Room; factory Room.fromJson(Map json) => _$RoomFromJson(json); } diff --git a/lib/models/room_chat.dart b/lib/models/room_chat.dart deleted file mode 100644 index a038a88..0000000 --- a/lib/models/room_chat.dart +++ /dev/null @@ -1,32 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/event.dart"; - -part "room_chat.freezed.dart"; -part "room_chat.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RoomChat({ - required final IList timeline, - required final bool hasMoreForward, - required final bool hasMoreBackward, - final HistoricalData? historicalData, -}) with _$RoomChat { - Map toJson() => _$RoomChatToJson(this); - - factory RoomChat.fromJson(Map json) => - _$RoomChatFromJson(json); -} - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const HistoricalData({ - required final String start, - required final String end, -}) with _$HistoricalData { - Map toJson() => _$HistoricalDataToJson(this); - - factory HistoricalData.fromJson(Map json) => - _$HistoricalDataFromJson(json); -} diff --git a/lib/models/room_metadata.dart b/lib/models/room_metadata.dart index 3aafd0e..7c16cae 100644 --- a/lib/models/room_metadata.dart +++ b/lib/models/room_metadata.dart @@ -1,31 +1,29 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/epoch_date_time_converter.dart"; import "package:nexus/models/lazy_load_summary.dart"; - part "room_metadata.freezed.dart"; part "room_metadata.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RoomMetadata({ - @JsonKey(name: "room_id") required final String id, +@freezed +abstract class RoomMetadata with _$RoomMetadata { + const factory RoomMetadata({ + @JsonKey(name: "room_id") required String id, - // CreateEventContent creationContent, - // TombstoneEventContent tombstoneEventContent, - final String? name, - final Uri? avatar, - final String? dmUserId, - final String? topic, - final String? canonicalAlias, - final LazyLoadSummary? lazyLoadSummary, - required final bool hasMemberList, - @JsonKey(name: "preview_event_rowid") required final int previewEventRowID, - @EpochDateTimeConverter() required final DateTime sortingTimestamp, - required final int unreadHighlights, - required final int unreadNotifications, - required final int unreadMessages, -}) with _$RoomMetadata { - Map toJson() => _$RoomMetadataToJson(this); + // required CreateEventContent creationContent, + // required TombstoneEventContent tombstoneEventContent, + String? name, + Uri? avatar, + String? dmUserId, + String? topic, + String? canonicalAlias, + LazyLoadSummary? lazyLoadSummary, + required bool hasMemberList, + @JsonKey(name: "preview_event_rowid") required int previewEventRowID, + @EpochDateTimeConverter() required DateTime sortingTimestamp, + required int unreadHighlights, + required int unreadNotifications, + required int unreadMessages, + }) = _RoomMetadata; factory RoomMetadata.fromJson(Map json) => _$RoomMetadataFromJson(json); diff --git a/lib/models/room_summary.dart b/lib/models/room_summary.dart deleted file mode 100644 index 7c2d6d2..0000000 --- a/lib/models/room_summary.dart +++ /dev/null @@ -1,25 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/content/create.dart"; -import "package:nexus/models/join_rule.dart"; - -part "room_summary.freezed.dart"; -part "room_summary.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const RoomSummary({ - required final String roomId, - @JsonKey(name: "num_joined_members") required final int joinedMembers, - final JoinRule? joinRule, - final String? name, - final Uri? avatarUrl, - final String? canonicalAlias, - final String? topic, - final String? roomVersion, - @JsonKey(unknownEnumValue: RoomType.room) final RoomType? roomType, -}) with _$RoomSummary { - Map toJson() => _$RoomSummaryToJson(this); - - factory RoomSummary.fromJson(Map json) => - _$RoomSummaryFromJson(json); -} diff --git a/lib/models/setting.dart b/lib/models/setting.dart index 2ff541d..6a49845 100644 --- a/lib/models/setting.dart +++ b/lib/models/setting.dart @@ -1,13 +1,16 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; -class Setting({ - required final String title, - required final String description, - required final IconData icon, - required final Widget Function( - String title, - String description, - IconData icon, - ) - builder, -}); +class Setting { + final String title; + final String description; + final IconData icon; + final Widget Function(String title, String description, IconData icon) + builder; + + Setting({ + required this.title, + required this.description, + required this.builder, + required this.icon, + }); +} diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 7f73375..eddc2da 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -1,17 +1,15 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "settings.freezed.dart"; part "settings.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const Settings({ - final ThemeMode theme = ThemeMode.system, - final bool useDynamicTheming = true, - final bool linuxMobileMode = false, -}) with _$Settings { - Map toJson() => _$SettingsToJson(this); +@freezed +abstract class Settings with _$Settings { + const factory Settings({ + @Default(ThemeMode.system) ThemeMode theme, + @Default(true) bool useDynamicTheming, + @Default(false) bool linuxMobileMode, + }) = _Settings; factory Settings.fromJson(Map json) => _$SettingsFromJson(json); diff --git a/lib/models/settings_category.dart b/lib/models/settings_category.dart index 2dc7076..88bfdd0 100644 --- a/lib/models/settings_category.dart +++ b/lib/models/settings_category.dart @@ -1,13 +1,14 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/setting.dart"; - part "settings_category.freezed.dart"; @freezed -class const SettingsCategory({ - required final String title, - required final IconData icon, - required final IList settings, -}) with _$SettingsCategory; +abstract class SettingsCategory with _$SettingsCategory { + const factory SettingsCategory({ + required String title, + required IconData icon, + required IList settings, + }) = _SettingsCategory; +} diff --git a/lib/models/space.dart b/lib/models/space.dart index 97efe7a..73fbbc6 100644 --- a/lib/models/space.dart +++ b/lib/models/space.dart @@ -3,15 +3,16 @@ import "package:flutter/widgets.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/room.dart"; import "package:nexus/models/subspace.dart"; - part "space.freezed.dart"; @freezed -class const Space({ - required final String id, - required final String title, - final IconData? icon, - final Room? room, - required final IList children, - required final IList subSpaces, -}) with _$Space; +abstract class Space with _$Space { + const factory Space({ + required String id, + required String title, + IconData? icon, + Room? room, + required IList children, + required IList subSpaces, + }) = _Space; +} diff --git a/lib/models/space_edge.dart b/lib/models/space_edge.dart index ea74130..192af31 100644 --- a/lib/models/space_edge.dart +++ b/lib/models/space_edge.dart @@ -1,15 +1,13 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "space_edge.freezed.dart"; part "space_edge.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SpaceEdge({ - required final String childId, - final bool suggested = false, -}) with _$SpaceEdge { - Map toJson() => _$SpaceEdgeToJson(this); +@freezed +abstract class SpaceEdge with _$SpaceEdge { + const factory SpaceEdge({ + required String childId, + @Default(false) bool suggested, + }) = _SpaceEdge; factory SpaceEdge.fromJson(Map json) => _$SpaceEdgeFromJson(json); diff --git a/lib/models/spec_versions_response.dart b/lib/models/spec_versions_response.dart index 1f414d2..07a3ada 100644 --- a/lib/models/spec_versions_response.dart +++ b/lib/models/spec_versions_response.dart @@ -1,27 +1,24 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; - part "spec_versions_response.freezed.dart"; part "spec_versions_response.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SpecVersionsResponse({ - required final IList versions, - required final UnstableFeatures unstableFeatures, -}) with _$SpecVersionsResponse { - Map toJson() => _$SpecVersionsResponseToJson(this); +@freezed +abstract class SpecVersionsResponse with _$SpecVersionsResponse { + const factory SpecVersionsResponse({ + required IList versions, + required UnstableFeatures unstableFeatures, + }) = _SpecVersionsResponse; factory SpecVersionsResponse.fromJson(Map json) => _$SpecVersionsResponseFromJson(json); } -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const UnstableFeatures({ - @JsonKey(name: "uk.timedout.msc4494") final bool msc4494 = false, -}) with _$UnstableFeatures { - Map toJson() => _$UnstableFeaturesToJson(this); +@freezed +abstract class UnstableFeatures with _$UnstableFeatures { + const factory UnstableFeatures({ + @JsonKey(name: "uk.timedout.msc4494") @Default(false) bool msc4494, + }) = _UnstableFeatures; factory UnstableFeatures.fromJson(Map json) => _$UnstableFeaturesFromJson(json); diff --git a/lib/models/subspace.dart b/lib/models/subspace.dart index 97d1de2..1a1879c 100644 --- a/lib/models/subspace.dart +++ b/lib/models/subspace.dart @@ -1,11 +1,10 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/room.dart"; - part "subspace.freezed.dart"; @freezed -class const Subspace({ - required final Room room, - required final IList children, -}) with _$Subspace; +abstract class Subspace with _$Subspace { + const factory Subspace({required Room room, required IList children}) = + _Subspace; +} diff --git a/lib/models/sync_data.dart b/lib/models/sync_data.dart index a9c10bf..b2699d6 100644 --- a/lib/models/sync_data.dart +++ b/lib/models/sync_data.dart @@ -2,21 +2,20 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/room.dart"; import "package:nexus/models/space_edge.dart"; - part "sync_data.freezed.dart"; part "sync_data.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SyncData({ - final bool clearState = false, - final IMap> accountData = const IMap.empty(), - final IMap rooms = const IMap.empty(), - final ISet leftRooms = const ISet.empty(), - final IMap>? spaceEdges, - final IList? topLevelSpaces, -}) with _$SyncData { - Map toJson() => _$SyncDataToJson(this); +@freezed +abstract class SyncData with _$SyncData { + const factory SyncData({ + @Default(false) bool clearState, + @Default(IMap.empty()) IMap> accountData, + @Default(IMap.empty()) IMap rooms, + @Default(ISet.empty()) ISet leftRooms, + // required IList invitedRooms, + IMap>? spaceEdges, + IList? topLevelSpaces, + }) = _SyncData; factory SyncData.fromJson(Map json) => _$SyncDataFromJson(json); diff --git a/lib/models/sync_status.dart b/lib/models/sync_status.dart index 57bd882..7848fbe 100644 --- a/lib/models/sync_status.dart +++ b/lib/models/sync_status.dart @@ -1,16 +1,14 @@ import "package:freezed_annotation/freezed_annotation.dart"; - part "sync_status.freezed.dart"; part "sync_status.g.dart"; -@Freezed(toJson: false, fromJson: false) -@JsonSerializable() -class const SyncStatus({ - required final SyncStatusType type, - required final String? error, - required final int errorCount, -}) with _$SyncStatus { - Map toJson() => _$SyncStatusToJson(this); +@freezed +abstract class SyncStatus with _$SyncStatus { + const factory SyncStatus({ + required SyncStatusType type, + String? error, + required int errorCount, + }) = _SyncStatus; factory SyncStatus.fromJson(Map json) => _$SyncStatusFromJson(json); diff --git a/lib/widgets/pages/chat.dart b/lib/pages/chat.dart similarity index 56% rename from lib/widgets/pages/chat.dart rename to lib/pages/chat.dart index 186dfa2..316c9af 100644 --- a/lib/widgets/pages/chat.dart +++ b/lib/pages/chat.dart @@ -1,23 +1,25 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/emoji.dart"; import "package:nexus/controllers/init_complete.dart"; -import "package:nexus/controllers/jump_to_event.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/sidebar.dart"; -import "package:nexus/widgets/room_chat/room_chat.dart"; +import "package:nexus/widgets/room_chat.dart"; import "package:nexus/widgets/loading.dart"; -class const ChatPage({super.key}) extends HookConsumerWidget { +class ChatPage extends ConsumerWidget { + const ChatPage({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (context, constraints) { final isDesktop = constraints.maxWidth > 650; final showMembersByDefault = constraints.maxWidth > 1000; final initComplete = ref.watch(InitCompleteController.provider); - final roomId = ref - .watch(KeyController.provider(KeyController.roomKey)) - .requireValue; + final roomId = ref.watch(KeyController.provider(KeyController.roomKey)); + + ref.read(EmojiController.provider); return SafeArea( child: Scaffold( @@ -27,19 +29,10 @@ class const ChatPage({super.key}) extends HookConsumerWidget { children: [ if (isDesktop) Sidebar(isDesktop: isDesktop), Expanded( - child: Consumer( - builder: (context, ref, _) { - final initialHighlight = ref.watch( - JumpToEventController.provider(roomId), - ); - return RoomChat( - key: ValueKey((roomId, initialHighlight)), - roomId: roomId, - isDesktop: isDesktop, - initialHighlightedEvent: initialHighlight, - showMembersByDefault: showMembersByDefault, - ); - }, + child: RoomChat( + roomId: roomId, + isDesktop: isDesktop, + showMembersByDefault: showMembersByDefault, ), ), ], diff --git a/lib/widgets/pages/select_server.dart b/lib/pages/select_server.dart similarity index 69% rename from lib/widgets/pages/select_server.dart rename to lib/pages/select_server.dart index 34f1a50..f7cefb8 100644 --- a/lib/widgets/pages/select_server.dart +++ b/lib/pages/select_server.dart @@ -1,5 +1,5 @@ import "package:app_links/app_links.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; @@ -9,11 +9,13 @@ import "package:nexus/controllers/client_id.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/main.dart"; import "package:nexus/models/homeserver.dart"; -import "package:nexus/widgets/pages/settings.dart"; +import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; -class const SelectServerPage({super.key}) extends HookConsumerWidget { +class SelectServerPage extends HookConsumerWidget { + const SelectServerPage({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -24,17 +26,6 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget { final homeserverUrl = useTextEditingController(); Future setHomeserver(Uri? newHomeserver) async { - if (newHomeserver == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Failed to parse homeserver URL. Are you sure you typed it correctly?", - style: .new(color: theme.colorScheme.onErrorContainer), - ), - backgroundColor: theme.colorScheme.errorContainer, - ), - ); - } isLoading.value = true; try { @@ -42,14 +33,26 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget { newHomeserver = Uri.https(newHomeserver!.path); } - final newUrl = await ref - .read(ClientController.provider.notifier) - .discoverHomeserver(newHomeserver!); + final newUrl = newHomeserver == null + ? null + : await ref + .read(ClientController.provider.notifier) + .discoverHomeserver(newHomeserver); if (context.mounted) { - Future tryLogin(Uri url) async { + if (newUrl == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Homeserver verification failed. Is your homeserver down?", + style: .new(color: theme.colorScheme.onErrorContainer), + ), + backgroundColor: theme.colorScheme.errorContainer, + ), + ); + } else { final codeResponse = await ref.watch( - AuthUrlController.provider(url).future, + AuthUrlController.provider(newUrl).future, ); await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url); @@ -57,60 +60,33 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget { AppLinks().uriLinkStream.listen((encodedUri) async { final state = encodedUri.queryParameters["state"]; if (state != codeResponse.state) return; - isLoading.value = true; - try { - final code = encodedUri.queryParameters["code"]; + final code = encodedUri.queryParameters["code"]; - if (code != null) { - await ref - .watch(ClientController.provider.notifier) - .exchangeToken( - .new( - homeserverUrl: url, - codeVerifier: codeResponse.codeVerifier, - redirectUri: .new( - scheme: "nexus.federated.nexus", - path: "/", - ), - code: code, - clientId: await ref.watch( - ClientIdController.provider(url).future, - ), + if (code != null) { + await ref + .watch(ClientController.provider.notifier) + .exchangeToken( + .new( + homeserverUrl: newUrl, + codeVerifier: codeResponse.codeVerifier, + redirectUri: .new( + scheme: "nexus.federated.nexus", + path: "/", ), - ); - } - } catch (error, stackTrace) { - showError(error, stackTrace); - rethrow; - } finally { - isLoading.value = false; + code: code, + clientId: await ref.watch( + ClientIdController.provider(newUrl).future, + ), + ), + ) + .onError(showError); } }); } - - if (newUrl == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Homeserver verification failed.", - style: .new(color: theme.colorScheme.onErrorContainer), - ), - action: SnackBarAction( - onPressed: () => tryLogin(newHomeserver!).onError(showError), - label: "Attempt log in anyways", - textColor: theme.colorScheme.onErrorContainer, - ), - backgroundColor: theme.colorScheme.errorContainer, - ), - ); - } else { - await tryLogin(newUrl); - } } } catch (error, stackTrace) { showError(error, stackTrace); - rethrow; } finally { isLoading.value = false; } @@ -135,7 +111,7 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget { children: [ Row( children: [ - SvgPicture.asset("assets/bundled/icon.svg", width: 128), + SvgPicture.asset("assets/icon.svg", width: 128), SizedBox(width: 12), Expanded( child: Column( @@ -182,20 +158,23 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget { ...([ .new( name: "Matrix.org", - description: "The Matrix.org Foundation offers the matrix.org homeserver as an easy entry point for anyone wanting to try out Matrix.", + description: + "The Matrix.org Foundation offers the matrix.org homeserver as an easy entry point for anyone wanting to try out Matrix.", url: .https("matrix.org"), iconUrl: "https://raw.githubusercontent.com/element-hq/logos/refs/heads/master/matrix/matrix-favicon${Theme.brightnessOf(context) == Brightness.dark ? "-white" : ""}.png", ), .new( name: "Federated Nexus", - description: "Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo. By the same developers who made Nexus client.", + description: + "Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo. By the same developers who made Nexus client.", url: .https("federated.nexus"), iconUrl: "https://federated.nexus/images/icon.png", ), .new( name: "Unredacted", - description: "Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.", + description: + "Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.", url: .https("unredacted.org", "services/si/matrix"), iconUrl: "https://unredacted.org/favicon.ico", ), diff --git a/lib/widgets/pages/settings.dart b/lib/pages/settings.dart similarity index 81% rename from lib/widgets/pages/settings.dart rename to lib/pages/settings.dart index 50eb7f0..fa4d8da 100644 --- a/lib/widgets/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,6 +1,6 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_card_list/m3e_card_list.dart"; @@ -8,12 +8,14 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/settings_sections.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/show_about_dialog.dart"; -import "package:nexus/widgets/pages/settings_category.dart"; +import "package:nexus/pages/settings_category.dart"; import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/highlight_wrapper.dart"; import "package:super_sliver_list/super_sliver_list.dart"; -class const SettingsPage({super.key}) extends ConsumerWidget { +class SettingsPage extends ConsumerWidget { + const SettingsPage({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (_, constraints) => HookBuilder( @@ -140,12 +142,12 @@ class const SettingsPage({super.key}) extends ConsumerWidget { vertical: 8, ), margin: .symmetric(horizontal: 12), - color: Theme.of(context) - .colorScheme - .primaryContainer, + color: Theme.of( + context, + ).colorScheme.primaryContainer, itemCount: categories.length, - onTap: (index) => Navigator.of(context) - .push( + onTap: (index) => + Navigator.of(context).push( MaterialPageRoute( builder: (context) => SettingsCategoryPage( @@ -170,34 +172,30 @@ class const SettingsPage({super.key}) extends ConsumerWidget { ) : Row( children: [ - MaterialUiCompatibilityBridge( - child: NavigationRailM3E( - type: .alwaysExpand, - trailing: searchBar, - scrollable: true, - sections: sections - .mapTo( - ( - categoryGroup, - categories, - ) => NavigationRailM3ESection( - header: DividerText(categoryGroup), - destinations: categories - .map( - (category) => - NavigationRailM3EDestination( - icon: Icon(category.icon), - label: category.title, - ), - ) - .toList(), - ), - ) - .toList(), - selectedIndex: selected.value, - onDestinationSelected: (value) => - selected.value = value, - ), + NavigationRailM3E( + type: .alwaysExpand, + trailing: searchBar, + scrollable: true, + sections: sections + .mapTo( + (categoryGroup, categories) => + NavigationRailM3ESection( + header: DividerText(categoryGroup), + destinations: categories + .map( + (category) => + NavigationRailM3EDestination( + icon: Icon(category.icon), + label: category.title, + ), + ) + .toList(), + ), + ) + .toList(), + selectedIndex: selected.value, + onDestinationSelected: (value) => + selected.value = value, ), VerticalDivider(), Expanded( diff --git a/lib/widgets/pages/settings_category.dart b/lib/pages/settings_category.dart similarity index 91% rename from lib/widgets/pages/settings_category.dart rename to lib/pages/settings_category.dart index 8e94c56..b21d048 100644 --- a/lib/widgets/pages/settings_category.dart +++ b/lib/pages/settings_category.dart @@ -1,7 +1,7 @@ import "dart:async"; import "package:collection/collection.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/settings_sections.dart"; @@ -9,11 +9,11 @@ import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/widgets/highlight_wrapper.dart"; import "package:super_sliver_list/super_sliver_list.dart"; -class const SettingsCategoryPage( - final int index, { - final int? initialHighlight, - super.key, -}) extends HookConsumerWidget { +class SettingsCategoryPage extends HookConsumerWidget { + final int index; + final int? initialHighlight; + const SettingsCategoryPage(this.index, {this.initialHighlight, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final highlight = useState(initialHighlight); diff --git a/lib/widgets/pages/verify.dart b/lib/pages/verify.dart similarity index 93% rename from lib/widgets/pages/verify.dart rename to lib/pages/verify.dart index a4f95ec..4eefa6c 100644 --- a/lib/widgets/pages/verify.dart +++ b/lib/pages/verify.dart @@ -1,12 +1,14 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; -import "package:nexus/widgets/pages/settings.dart"; +import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/helpers/required_validator_helper.dart"; -class const VerifyPage({super.key}) extends HookConsumerWidget { +class VerifyPage extends HookConsumerWidget { + const VerifyPage({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final passphraseController = useTextEditingController(); diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index 33727ac..9da03aa 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -1,20 +1,28 @@ import "dart:io"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/settings.dart"; import "package:window_manager/window_manager.dart"; -final class const Appbar({ - final Widget? leading, - final Widget? title, - final Color? backgroundColor, - final double? scrolledUnderElevation, - final IList actions = const .empty(), - final VoidCallback? onTap, - super.key, -}) extends ConsumerWidget implements PreferredSizeWidget { +class Appbar extends ConsumerWidget implements PreferredSizeWidget { + final Widget? leading; + final Widget? title; + final Color? backgroundColor; + final double? scrolledUnderElevation; + final IList actions; + final VoidCallback? onTap; + + const Appbar({ + super.key, + this.title, + this.onTap, + this.backgroundColor, + this.scrolledUnderElevation, + this.leading, + this.actions = const .empty(), + }); + @override Size get preferredSize => const .fromHeight(kToolbarHeight); @@ -39,7 +47,7 @@ final class const Appbar({ : (_) => windowManager.startDragging(), ), child: AppBar( - leading: leading == null ? null : InkWell(onTap: onTap, child: leading), + leading: InkWell(onTap: onTap, child: leading), backgroundColor: backgroundColor, scrolledUnderElevation: scrolledUnderElevation, actionsPadding: const .symmetric(horizontal: 8), diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart index 982d56e..75c08b9 100644 --- a/lib/widgets/avatar_or_hash.dart +++ b/lib/widgets/avatar_or_hash.dart @@ -1,15 +1,21 @@ import "package:color_hash/color_hash.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/helpers/mxc_image.dart"; -final class const AvatarOrHash( - final Uri? avatar, - final String title, { - final Widget? fallback, - final double height = 24, - super.key, -}) extends ConsumerWidget { +class AvatarOrHash extends ConsumerWidget { + final Uri? avatar; + final String title; + final Widget? fallback; + final double height; + const AvatarOrHash( + this.avatar, + this.title, { + this.fallback, + this.height = 24, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final box = ColoredBox( diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index 3d04a78..ca3de8a 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -1,9 +1,7 @@ import "dart:io"; - import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:file_selector/file_selector.dart"; -import "package:nexus/widgets/emoji_picker.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:fluttertagger/fluttertagger.dart"; @@ -14,24 +12,33 @@ import "package:nexus/controllers/power_level.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/relation_type.dart"; -import "package:nexus/widgets/composer/overlays/tagger_overlay.dart"; +import "package:nexus/widgets/composer/mention_overlay.dart"; import "package:nexus/widgets/composer/relation_preview.dart"; +import "package:nexus/widgets/emoji_picker_button.dart"; import "package:nexus/main.dart"; -class const Composer( - final String roomId, { - required final Event? relatedEvent, - required final RelationType relationType, - required final VoidCallback onDismiss, - required final Future Function( +class Composer extends HookConsumerWidget { + final String roomId; + final Event? relatedEvent; + final RelationType relationType; + final VoidCallback onDismiss; + final FocusNode? node; + final Future Function( String text, { required bool shouldMention, required IList tags, }) - onSend, - final FocusNode? node, - super.key, -}) extends HookConsumerWidget { + onSend; + const Composer( + this.roomId, { + required this.relatedEvent, + required this.relationType, + required this.onDismiss, + required this.onSend, + this.node, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -119,19 +126,10 @@ class const Composer( ), ) ? [ - IconButton( - onPressed: () => showModalBottomSheet( - isScrollControlled: true, - context: context, - builder: (context) => EmojiPicker( - onSelection: (value) { - Navigator.of(context).pop(); - controller.value.text += value; - node?.requestFocus(); - }, - ), - ), - icon: Icon(Icons.emoji_emotions), + EmojiPickerButton( + context: context, + onSelection: (_) => node?.requestFocus(), + controller: controller.value, ), PopupMenuButton( tooltip: "Add media", @@ -145,8 +143,9 @@ class const Composer( ), onTap: () async => ref .watch( - AttachmentController.provider(roomId) - .notifier, + AttachmentController.provider( + roomId, + ).notifier, ) .add( (await ref @@ -164,8 +163,9 @@ class const Composer( ), onTap: () async => ref .watch( - AttachmentController.provider(roomId) - .notifier, + AttachmentController.provider( + roomId, + ).notifier, ) .add( (await ref @@ -179,8 +179,9 @@ class const Composer( PopupMenuItem( onTap: () async => ref .watch( - AttachmentController.provider(roomId) - .notifier, + AttachmentController.provider( + roomId, + ).notifier, ) .add((await openFile())!) .onError(showError), @@ -195,9 +196,9 @@ class const Composer( Expanded( child: FlutterTagger( triggerStrategy: .eager, - overlay: TaggerOverlay( - query.value, - roomId: roomId, + overlay: MentionOverlay( + roomId, + query: query.value, triggerCharacter: triggerCharacter.value, addTag: ({required id, required name}) { controller.value.addTag(id: id, name: name); @@ -212,7 +213,6 @@ class const Composer( triggerCharacterAndStyles: { "@": style, "#": style, - ":": style, }, builder: (context, key) => Focus( onKeyEvent: (_, event) { @@ -234,12 +234,7 @@ class const Composer( child: TextField( maxLines: 12, minLines: 1, - autofocus: - (Platform.isLinux || - Platform.isMacOS || - Platform.isWindows) - ? true - : false, + autofocus: true, decoration: .new( hintText: "Your message here...", border: .none, diff --git a/lib/widgets/composer/mention_overlay.dart b/lib/widgets/composer/mention_overlay.dart new file mode 100644 index 0000000..ca4f95a --- /dev/null +++ b/lib/widgets/composer/mention_overlay.dart @@ -0,0 +1,158 @@ +import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/members_by_status.dart"; +import "package:nexus/controllers/rooms.dart"; +import "package:nexus/controllers/via.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/helpers/extensions/get_localpart.dart"; +import "package:nexus/models/content/membership.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; +import "package:nexus/widgets/loading.dart"; + +class MentionOverlay extends ConsumerWidget { + final String? triggerCharacter; + final String query; + final String roomId; + final void Function({required String id, required String name}) addTag; + const MentionOverlay( + this.roomId, { + required this.query, + required this.addTag, + required this.triggerCharacter, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final rooms = ref.watch(RoomsController.provider); + + return Padding( + padding: .all(8), + child: ClipRRect( + borderRadius: .all(.circular(12)), + child: Container( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + padding: .all(8), + child: switch (triggerCharacter) { + "@" => + ref + .watch( + MembersByStatusController.provider( + .new(roomId: roomId, status: .join), + ), + ) + .betterWhen( + data: (members) => ListView( + children: + (query.isEmpty + ? members + : members.where( + (member) => + member.stateKey + ?.toLowerCase() + .contains( + query.toLowerCase(), + ) == + true || + switch (member.content) { + MembershipContent( + :final displayName, + ) => + displayName + ?.toLowerCase() + .contains( + query.toLowerCase(), + ) == + true, + _ => false, + }, + )) + .map( + (member) => switch (member.content) { + MembershipContent( + :final displayName, + :final avatarUrl, + ) => + Material( + color: Colors.transparent, + child: ListTile( + leading: AvatarOrHash( + avatarUrl, + displayName ?? + member.stateKey!.localpart, + ), + title: Text( + displayName ?? + member.stateKey!.localpart, + ), + subtitle: Text(member.stateKey!), + onTap: () => addTag( + id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})", + name: member.stateKey!.localpart, + ), + ), + ), + _ => SizedBox.shrink(), + }, + ) + .toList(), + ), + ), + "#" => ListView( + children: + (query.isEmpty + ? rooms.values + : rooms.values.where( + (room) => + (room.metadata?.name ?? + room.metadata?.id ?? + "") + .toLowerCase() + .contains(query.toLowerCase()), + )) + .map((room) { + final name = + room.metadata?.name ?? + room.metadata?.canonicalAlias ?? + room.metadata?.id ?? + "Unknown Room"; + return Material( + color: Colors.transparent, + child: ListTile( + leading: AvatarOrHash( + room.metadata?.avatar, + name, + fallback: Icon(Icons.numbers), + ), + title: Text(name), + subtitle: room.metadata?.topic == null + ? null + : Text(room.metadata!.topic!, maxLines: 1), + onTap: () { + final vias = ref.watch( + ViaController.provider(room), + ); + addTag( + id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)", + name: + (room.metadata?.canonicalAlias ?? + room.metadata?.id) + ?.substring(1) + .split(":") + .first ?? + "", + ); + }, + ), + ); + }) + .toList(), + ), + + _ => Loading(), + }, + ), + ), + ); + } +} diff --git a/lib/widgets/composer/overlays/emoji_overlay.dart b/lib/widgets/composer/overlays/emoji_overlay.dart deleted file mode 100644 index 0e762c0..0000000 --- a/lib/widgets/composer/overlays/emoji_overlay.dart +++ /dev/null @@ -1,47 +0,0 @@ -import "package:collection/collection.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_emoji_picker/material_emoji_picker.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; - -class const EmojiOverlay( - final String query, { - required final String roomId, - required final void Function({required String id, required String name}) - addTag, - super.key, -}) extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch(EmojiController.provider) - .betterWhen( - data: (emojis) => ListView( - children: emojis - .map((element) => element.emojis) - .flattened - .where( - (emoji) => - emoji.aliases.join().contains(query) || - emoji.description.contains(query) || - emoji.tags.join().contains(query), - ) - .map( - (emoji) => ListTile( - leading: SizedBox.square( - dimension: 28, - child: FittedBox(child: emoji.widget), - ), - title: Text(emoji.aliases.first), - subtitle: Text(emoji.description), - onTap: () => addTag( - id: Uri.tryParse(emoji.value)?.scheme == "mxc" - ? "" // TODO: Handle custom emotes - : emoji.value, - name: emoji.aliases.first, - ), - ), - ) - .toList(), - ), - ); -} diff --git a/lib/widgets/composer/overlays/room_overlay.dart b/lib/widgets/composer/overlays/room_overlay.dart deleted file mode 100644 index b5af177..0000000 --- a/lib/widgets/composer/overlays/room_overlay.dart +++ /dev/null @@ -1,58 +0,0 @@ -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/controllers/via.dart"; -import "package:nexus/widgets/avatar_or_hash.dart"; - -class const RoomOverlay( - final String query, { - required final void Function({required String id, required String name}) - addTag, - super.key, -}) extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final rooms = ref.watch(RoomsController.provider); - return ListView( - children: - (query.isEmpty - ? rooms.values - : rooms.values.where( - (room) => (room.metadata?.name ?? room.metadata?.id ?? "") - .toLowerCase() - .contains(query.toLowerCase()), - )) - .map((room) { - final name = - room.metadata?.name ?? - room.metadata?.canonicalAlias ?? - room.metadata?.id ?? - "Unknown Room"; - return ListTile( - leading: AvatarOrHash( - room.metadata?.avatar, - name, - fallback: Icon(Icons.numbers), - ), - title: Text(name), - subtitle: room.metadata?.topic == null - ? null - : Text(room.metadata!.topic!, maxLines: 1), - onTap: () { - final vias = ref.watch(ViaController.provider(room)); - addTag( - id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)", - name: - (room.metadata?.canonicalAlias ?? room.metadata?.id) - ?.substring(1) - .split(":") - .first ?? - "", - ); - }, - ); - }) - .toList(), - ); - } -} diff --git a/lib/widgets/composer/overlays/tagger_overlay.dart b/lib/widgets/composer/overlays/tagger_overlay.dart deleted file mode 100644 index 7d973dc..0000000 --- a/lib/widgets/composer/overlays/tagger_overlay.dart +++ /dev/null @@ -1,39 +0,0 @@ -import "package:material_ui/material_ui.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/widgets/composer/overlays/room_overlay.dart"; -import "package:nexus/widgets/composer/overlays/user_overlay.dart"; -import "package:nexus/widgets/composer/overlays/emoji_overlay.dart"; -import "package:nexus/widgets/loading.dart"; - -class const TaggerOverlay( - final String query, { - required final String roomId, - required final void Function({required String id, required String name}) - addTag, - required final String? triggerCharacter, - super.key, -}) extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - return Padding( - padding: .all(8), - child: ClipRRect( - borderRadius: .all(.circular(12)), - child: Container( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - padding: .all(8), - child: Material( - color: Colors.transparent, - child: switch (triggerCharacter) { - "@" => UserOverlay(query, roomId: roomId, addTag: addTag), - "#" => RoomOverlay(query, addTag: addTag), - ":" => EmojiOverlay(query, roomId: roomId, addTag: addTag), - - _ => Loading(), - }, - ), - ), - ), - ); - } -} diff --git a/lib/widgets/composer/overlays/user_overlay.dart b/lib/widgets/composer/overlays/user_overlay.dart deleted file mode 100644 index fe4f70f..0000000 --- a/lib/widgets/composer/overlays/user_overlay.dart +++ /dev/null @@ -1,64 +0,0 @@ -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/controllers/members_by_status.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/helpers/extensions/get_localpart.dart"; -import "package:nexus/models/content/membership.dart"; -import "package:nexus/widgets/avatar_or_hash.dart"; - -class const UserOverlay( - final String query, { - required final String roomId, - required final void Function({required String id, required String name}) - addTag, - super.key, -}) extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch( - MembersByStatusController.provider(.new(roomId: roomId, status: .join)), - ) - .betterWhen( - data: (members) => ListView( - children: - (query.isEmpty - ? members - : members.where( - (member) => - member.stateKey?.toLowerCase().contains( - query.toLowerCase(), - ) == - true || - switch (member.content) { - MembershipContent(:final displayName) => - displayName?.toLowerCase().contains( - query.toLowerCase(), - ) == - true, - _ => false, - }, - )) - .map( - (member) => switch (member.content) { - MembershipContent(:final displayName, :final avatarUrl) => - ListTile( - leading: AvatarOrHash( - avatarUrl, - displayName ?? member.stateKey!.localpart, - ), - title: Text( - displayName ?? member.stateKey!.localpart, - ), - subtitle: Text(member.stateKey!), - onTap: () => addTag( - id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})", - name: member.stateKey!.localpart, - ), - ), - _ => SizedBox.shrink(), - }, - ) - .toList(), - ), - ); -} diff --git a/lib/widgets/composer/relation_preview.dart b/lib/widgets/composer/relation_preview.dart index fca9de6..c9cc271 100644 --- a/lib/widgets/composer/relation_preview.dart +++ b/lib/widgets/composer/relation_preview.dart @@ -1,17 +1,25 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/widgets/event_preview.dart"; -class const RelationPreview( - final Event? relatedEvent, { - required final RelationType relationType, - required final VoidCallback onDismiss, - required final bool shouldMention, - required final VoidCallback toggleShouldMention, - super.key, -}) extends ConsumerWidget { +class RelationPreview extends ConsumerWidget { + final Event? relatedEvent; + final RelationType relationType; + final VoidCallback onDismiss; + final bool shouldMention; + final VoidCallback toggleShouldMention; + + const RelationPreview( + this.relatedEvent, { + required this.relationType, + required this.onDismiss, + required this.shouldMention, + required this.toggleShouldMention, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { if (relatedEvent == null) return SizedBox.shrink(); diff --git a/lib/widgets/divider_text.dart b/lib/widgets/divider_text.dart index c44ae11..2b0f9bd 100644 --- a/lib/widgets/divider_text.dart +++ b/lib/widgets/divider_text.dart @@ -1,8 +1,11 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:nexus/widgets/divider_widget.dart"; -final class const DividerText(final String text, {super.key}) - extends StatelessWidget { +class DividerText extends StatelessWidget { + final String text; + + const DividerText(this.text, {super.key}); + @override Widget build(BuildContext context) => DividerWidget(Text(text, style: Theme.of(context).textTheme.labelLarge)); diff --git a/lib/widgets/divider_widget.dart b/lib/widgets/divider_widget.dart index 5e4972c..6f13bd4 100644 --- a/lib/widgets/divider_widget.dart +++ b/lib/widgets/divider_widget.dart @@ -1,7 +1,9 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class DividerWidget extends StatelessWidget { + final Widget widget; + const DividerWidget(this.widget, {super.key}); -final class const DividerWidget(final Widget widget, {super.key}) - extends StatelessWidget { @override Widget build(BuildContext context) => LayoutBuilder( builder: (_, constraints) => Row( diff --git a/lib/widgets/emoji_picker.dart b/lib/widgets/emoji_picker.dart deleted file mode 100644 index a0c691a..0000000 --- a/lib/widgets/emoji_picker.dart +++ /dev/null @@ -1,53 +0,0 @@ -import "dart:async"; - -import "package:collection/collection.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:material_emoji_picker/material_emoji_picker.dart" as upstream; -import "package:material_ui/material_ui.dart"; -import "package:nexus/controllers/recent_emoji.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/main.dart"; - -class const EmojiPicker({ - required final FutureOr Function(String value) onSelection, - final bool allowFreeText = false, - super.key, -}) extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final recentEmoji = ref.watch(RecentEmojiController.provider); - - return ref - .watch(upstream.EmojiController.provider) - .betterWhen( - data: (categories) => upstream.EmojiPicker( - onSelection: (emoji) async { - await onSelection(emoji); - await ref - .watch(RecentEmojiController.provider.notifier) - .add(emoji) - .onError(showError); - }, - allowFreeText: allowFreeText, - prependCategories: .new([ - .new( - icon: Icon(Icons.history), - name: "Recent", - emojis: .new( - recentEmoji - .map( - (recent) => categories - .map((element) => element.emojis) - .flattened - .firstWhereOrNull( - (element) => element.value == recent.emoji, - ), - ) - .nonNulls, - ), - ), - ]), - ), - ); - } -} diff --git a/lib/widgets/emoji_picker_button.dart b/lib/widgets/emoji_picker_button.dart new file mode 100644 index 0000000..2ac906a --- /dev/null +++ b/lib/widgets/emoji_picker_button.dart @@ -0,0 +1,52 @@ +import "package:emoji_text_field/emoji_text_field.dart"; +import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/emoji.dart"; + +class EmojiPickerButton extends HookConsumerWidget { + final TextEditingController? controller; + final void Function(String emoji)? onSelection; + final VoidCallback? onPressed; + final BuildContext context; + const EmojiPickerButton({ + this.controller, + this.onPressed, + this.onSelection, + required this.context, + super.key, + }); + + @override + Widget build(_, WidgetRef ref) => IconButton( + onPressed: () async { + onPressed?.call(); + final controller = this.controller ?? .new(); + + final emojis = await ref.watch(EmojiController.provider.future); + if (context.mounted) { + showModalBottomSheet( + context: context, + builder: (context) => EmojiKeyboardView( + config: .new( + showRecentTab: false, + customCategories: emojis.$1.unlock, + customKeywords: emojis.$2.unlock, + backgroundColor: Theme.of(context).colorScheme.surfaceContainer, + height: 600, + ), + textController: controller + ..addListener(() { + // Without this, there will sometimes be a debugLocked is not true error sometimes + // It might be preferable to use a microtask instead of a `Future.delayed`. + Future.delayed(.zero, () { + if (context.mounted) Navigator.of(context).pop(); + }); + onSelection?.call(controller.text); + }), + ), + ); + } + }, + icon: Icon(Icons.emoji_emotions), + ); +} diff --git a/lib/widgets/error_dialog.dart b/lib/widgets/error_dialog.dart index ddc4c98..9b62200 100644 --- a/lib/widgets/error_dialog.dart +++ b/lib/widgets/error_dialog.dart @@ -1,13 +1,13 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/misc.dart"; -final class const ErrorDialog( - final Object error, - final StackTrace? stackTrace, { - final ProviderOrFamily? provider, - super.key, -}) extends ConsumerWidget { +class ErrorDialog extends ConsumerWidget { + final Object error; + final StackTrace? stackTrace; + final ProviderOrFamily? provider; + const ErrorDialog(this.error, this.stackTrace, {this.provider, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { return AlertDialog( diff --git a/lib/widgets/event_preview.dart b/lib/widgets/event_preview.dart index 30dfbfe..7a40a75 100644 --- a/lib/widgets/event_preview.dart +++ b/lib/widgets/event_preview.dart @@ -1,12 +1,14 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; import "package:nexus/widgets/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/renderers/event.dart"; -class const EventPreview(final Event event, {super.key}) - extends StatelessWidget { +class EventPreview extends StatelessWidget { + final Event event; + const EventPreview(this.event, {super.key}); + @override Widget build(BuildContext context) => IgnorePointer( child: Padding( diff --git a/lib/widgets/expandable_image.dart b/lib/widgets/expandable_image.dart index 0639417..ee4f900 100644 --- a/lib/widgets/expandable_image.dart +++ b/lib/widgets/expandable_image.dart @@ -1,15 +1,15 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_buttons/m3e_buttons.dart"; import "package:nexus/helpers/mxc_image.dart"; import "package:nexus/models/requests/download_media.dart"; import "package:nexus/widgets/error_dialog.dart"; -final class const ExpandableImage( - final DownloadMediaRequest? request, { - required final Widget child, - super.key, -}) extends ConsumerWidget { +class ExpandableImage extends ConsumerWidget { + final Widget child; + final DownloadMediaRequest? request; + const ExpandableImage(this.request, {required this.child, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => InkWell( onTap: request == null diff --git a/lib/widgets/file_card.dart b/lib/widgets/file_card.dart index 4c0fdde..afdad89 100644 --- a/lib/widgets/file_card.dart +++ b/lib/widgets/file_card.dart @@ -1,13 +1,13 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:nexus/helpers/extensions/size_to_string.dart"; import "package:nexus/models/info/file.dart"; -final class const FileCard( - final Uri uri, - final FileInfo? info, { - final String? filename, - super.key, -}) extends StatelessWidget { +class FileCard extends StatelessWidget { + final Uri uri; + final FileInfo? info; + final String? filename; + const FileCard(this.uri, this.info, {this.filename, super.key}); + @override Widget build(BuildContext context) => SizedBox( width: 320, diff --git a/lib/widgets/highlight_wrapper.dart b/lib/widgets/highlight_wrapper.dart index b10e3ff..c7e568e 100644 --- a/lib/widgets/highlight_wrapper.dart +++ b/lib/widgets/highlight_wrapper.dart @@ -1,10 +1,10 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class HighlightWrapper extends StatelessWidget { + final Widget child; + final bool isHighlighted; + const HighlightWrapper(this.child, {this.isHighlighted = false, super.key}); -final class const HighlightWrapper( - final Widget child, { - final bool isHighlighted = false, - super.key, -}) extends StatelessWidget { @override Widget build(BuildContext context) => ClipRRect( borderRadius: .all(.circular(12)), diff --git a/lib/widgets/html/code_block.dart b/lib/widgets/html/code_block.dart index abd9be2..a5c3dee 100644 --- a/lib/widgets/html/code_block.dart +++ b/lib/widgets/html/code_block.dart @@ -1,13 +1,12 @@ import "dart:math"; -import "package:flutter/services.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class CodeBlock extends StatelessWidget { + final String code; + final String lang; + const CodeBlock(this.code, {required this.lang, super.key}); -class const CodeBlock( - final String code, { - required final String lang, - super.key, -}) extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -29,7 +28,7 @@ class const CodeBlock( ), ), TextButton.icon( - onPressed: () => Clipboard.setData(.new(text: code)), + onPressed: () {}, icon: Icon(Icons.copy), label: Text("Copy"), ), diff --git a/lib/widgets/html/html.dart b/lib/widgets/html/html.dart index 85f8054..5437285 100644 --- a/lib/widgets/html/html.dart +++ b/lib/widgets/html/html.dart @@ -1,5 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; @@ -11,140 +11,130 @@ import "package:nexus/widgets/html/spoiler_text.dart"; import "package:nexus/widgets/html/code_block.dart"; import "package:nexus/widgets/html/quoted.dart"; -class const Html( - final String html, { - final String? roomId, - final TextStyle? textStyle, - super.key, -}) extends ConsumerWidget { +class Html extends ConsumerWidget { + final String html; + final String? roomId; + final TextStyle? textStyle; + const Html(this.html, {this.roomId, this.textStyle, super.key}); + @override - Widget - build(BuildContext context, WidgetRef ref) - // needed until https://github.com/daohoangson/flutter_widget_from_html/issues/1618 is resolved - => MaterialUiCompatibilityBridge( - child: HtmlWidget( - html, - buildAsync: false, - textStyle: textStyle, - customWidgetBuilder: (element) { - if (element.attributes.keys.contains("data-mx-profile-fallback")) { - return SizedBox.shrink(); - } + Widget build(BuildContext context, WidgetRef ref) => HtmlWidget( + html, + buildAsync: false, + textStyle: textStyle, + customWidgetBuilder: (element) { + if (element.attributes.keys.contains("data-mx-profile-fallback")) { + return SizedBox.shrink(); + } - if (element.attributes.keys.contains("data-mx-spoiler")) { - return InlineCustomWidget(child: SpoilerText(element.text)); - } + if (element.attributes.keys.contains("data-mx-spoiler")) { + return InlineCustomWidget(child: SpoilerText(text: element.text)); + } - final height = - int.tryParse(element.attributes["height"] ?? "") ?? - (element.attributes.keys.contains("data-mx-emoticon") - ? 32 - : null) ?? - 300; - final width = int.tryParse(element.attributes["width"] ?? ""); - final src = Uri.tryParse(element.attributes["src"] ?? ""); + final height = + int.tryParse(element.attributes["height"] ?? "") ?? + (element.attributes.keys.contains("data-mx-emoticon") ? 32 : null) ?? + 300; + final width = int.tryParse(element.attributes["width"] ?? ""); + final src = Uri.tryParse(element.attributes["src"] ?? ""); - return switch (element.localName) { - "code" => - element.parent?.localName == "pre" - ? CodeBlock( - element.text, - lang: element.className.replaceAll("language-", ""), - ) - : null, + return switch (element.localName) { + "code" => + element.parent?.localName == "pre" + ? CodeBlock( + element.text, + lang: element.className.replaceAll("language-", ""), + ) + : null, - "blockquote" => Quoted( - Html(element.innerHtml, textStyle: textStyle, roomId: roomId), - ), + "blockquote" => Quoted( + Html(element.innerHtml, textStyle: textStyle, roomId: roomId), + ), - "a" => - element.attributes["href"]?.mention == null - ? null - : InlineCustomWidget( - child: MentionChip(element.attributes["href"]!, roomId), - ), + "a" => + element.attributes["href"]?.mention == null + ? null + : InlineCustomWidget( + child: MentionChip(element.attributes["href"]!, roomId), + ), - "img" => - src == null - ? SizedBox.shrink() - : InlineCustomWidget( - alignment: PlaceholderAlignment.middle, - child: ExpandableImage( - .new(mxc: src), - child: Image( - image: MxcImage(ref, .new(mxc: src)), - errorBuilder: (_, error, _) => Text( - "Image Failed to Load", - style: .new( - color: Theme.of(context).colorScheme.error, - ), - ), - height: height.toDouble(), - width: width?.toDouble(), - loadingBuilder: (_, child, loadingProgress) => - loadingProgress == null - ? child - : CircularProgressIndicator(), + "img" => + src == null + ? SizedBox.shrink() + : InlineCustomWidget( + alignment: PlaceholderAlignment.middle, + child: ExpandableImage( + .new(mxc: src), + child: Image( + image: MxcImage(ref, .new(mxc: src)), + errorBuilder: (_, error, _) => Text( + "Image Failed to Load", + style: .new(color: Theme.of(context).colorScheme.error), ), + height: height.toDouble(), + width: width?.toDouble(), + loadingBuilder: (_, child, loadingProgress) => + loadingProgress == null + ? child + : CircularProgressIndicator(), ), ), + ), - // Allowed elements list - ("del" || - "h1" || - "h2" || - "h3" || - "h4" || - "h5" || - "h6" || - "p" || - "ul" || - "ol" || - "sup" || - "sub" || - "li" || - "b" || - "i" || - "u" || - "strong" || - "em" || - "s" || - "code" || - "hr" || - "br" || - "div" || - "table" || - "thead" || - "tbody" || - "tr" || - "th" || - "td" || - "caption" || - "pre" || - "span" || - "details" || - "summary") => - null, + // Allowed elements list + ("del" || + "h1" || + "h2" || + "h3" || + "h4" || + "h5" || + "h6" || + "p" || + "ul" || + "ol" || + "sup" || + "sub" || + "li" || + "b" || + "i" || + "u" || + "strong" || + "em" || + "s" || + "code" || + "hr" || + "br" || + "div" || + "table" || + "thead" || + "tbody" || + "tr" || + "th" || + "td" || + "caption" || + "pre" || + "span" || + "details" || + "summary") => + null, - _ => SizedBox.shrink(), - }; - }, - customStylesBuilder: (element) => { - "width": "auto", - ...Map.fromEntries( - element.attributes - .mapTo?>( - (key, value) => switch (key) { - "data-mx-color" => .new("color", value), - "data-mx-bg-color" => .new("background-color", value), - _ => null, - }, - ) - .nonNulls, - ), - }, - onTapUrl: (url) => - ref.watch(LaunchHelper.provider).launchUrl(.parse(url)), - ), + _ => SizedBox.shrink(), + }; + }, + customStylesBuilder: (element) => { + "width": "auto", + ...Map.fromEntries( + element.attributes + .mapTo?>( + (key, value) => switch (key) { + "data-mx-color" => .new("color", value), + "data-mx-bg-color" => .new("background-color", value), + _ => null, + }, + ) + .nonNulls, + ), + }, + onTapUrl: (url) => ref.watch(LaunchHelper.provider).launchUrl(.parse(url)), ); } diff --git a/lib/widgets/html/mention_chip.dart b/lib/widgets/html/mention_chip.dart index 2387a49..f6105e3 100644 --- a/lib/widgets/html/mention_chip.dart +++ b/lib/widgets/html/mention_chip.dart @@ -1,60 +1,46 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/room_summary.dart"; import "package:nexus/controllers/user.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart"; -import "package:nexus/models/content/membership.dart"; -import "package:nexus/models/room_summary.dart"; -class const MentionChip(final String content, final String? roomId, {super.key}) - extends ConsumerWidget { +class MentionChip extends ConsumerWidget { + final String? roomId; + final String content; + const MentionChip(this.content, this.roomId, {super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final mention = content.mention; - final data = switch (mention?.characters.firstOrNull) { - "@" => - ref - .watch( - UserController.provider(.new(roomId: roomId, userId: mention!)), - ) - .whenOrNull(data: (data) => data), - - "#" || "!" => - ref - .watch( - RoomSummaryController.provider(.new(roomIdOrAlias: mention!)), - ) - .whenOrNull(data: (data) => data), - - _ => null, - }; + final membership = mention?.startsWith("@") == true + ? ref + .watch( + UserController.provider(.new(roomId: roomId, userId: mention!)), + ) + .whenOrNull(data: (data) => data) + : null; return mention == null ? SizedBox.shrink() : InkWell( onTap: () { - if (data case MembershipContent membership) { + if (membership != null) { context.showUserPopover(membership, mention, roomId: roomId); - } else if (data case RoomSummary summary) { - // TODO: Handle summary } }, - child: Text( - switch (data) { - RoomSummary summary => - (summary.name == null ? null : "#${summary.name}") ?? - summary.canonicalAlias ?? - summary.roomId, - MembershipContent membership => - membership.displayName == null - ? mention - : "@${membership.displayName}", - _ => mention, - }, - style: .new( - fontWeight: .bold, - color: Theme.of(context).colorScheme.primary, + child: IgnorePointer( + child: Chip( + label: Text( + (membership?.displayName == null + ? null + : "@${membership!.displayName}") ?? + mention, + style: .new( + fontWeight: .bold, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + backgroundColor: Theme.of(context).colorScheme.primary, ), ), ); diff --git a/lib/widgets/html/quoted.dart b/lib/widgets/html/quoted.dart index 3157263..e582b06 100644 --- a/lib/widgets/html/quoted.dart +++ b/lib/widgets/html/quoted.dart @@ -1,6 +1,9 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class Quoted extends StatelessWidget { + final Widget child; + const Quoted(this.child, {super.key}); -class const Quoted(final Widget child, {super.key}) extends StatelessWidget { @override Widget build(BuildContext context) => Container( decoration: BoxDecoration( diff --git a/lib/widgets/html/spoiler_text.dart b/lib/widgets/html/spoiler_text.dart index 92a5538..a7a457b 100644 --- a/lib/widgets/html/spoiler_text.dart +++ b/lib/widgets/html/spoiler_text.dart @@ -1,7 +1,11 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -class const SpoilerText(final String text, {super.key}) extends HookWidget { +class SpoilerText extends HookWidget { + final String text; + + const SpoilerText({super.key, required this.text}); + @override Widget build(BuildContext context) { final revealed = useState(false); diff --git a/lib/widgets/join_dialog.dart b/lib/widgets/join_dialog.dart index e818c7d..50b36f2 100644 --- a/lib/widgets/join_dialog.dart +++ b/lib/widgets/join_dialog.dart @@ -1,5 +1,5 @@ import "package:collection/collection.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; @@ -7,7 +7,10 @@ import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/spaces.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; -class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { +class JoinDialog extends HookWidget { + final WidgetRef ref; + const JoinDialog(this.ref, {super.key}); + @override Widget build(BuildContext context) { final roomAlias = useTextEditingController(); @@ -50,8 +53,9 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { .new( roomIdOrAlias: roomIdOrAlias, via: .new( - Uri.tryParse(roomAlias.text.replaceAll("/#", "")) - ?.queryParametersAll["via"] ?? + Uri.tryParse( + roomAlias.text.replaceAll("/#", ""), + )?.queryParametersAll["via"] ?? [], ), ), @@ -72,8 +76,9 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { await ref .watch( - KeyController.provider(KeyController.spaceKey) - .notifier, + KeyController.provider( + KeyController.spaceKey, + ).notifier, ) .set( space?.id ?? @@ -95,8 +100,9 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { if (space == null) { await ref .watch( - KeyController.provider(KeyController.roomKey) - .notifier, + KeyController.provider( + KeyController.roomKey, + ).notifier, ) .set(id); } @@ -109,9 +115,9 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { if (context.mounted) { scaffoldMessenger.showSnackBar( .new( - backgroundColor: Theme.of(context) - .colorScheme - .errorContainer, + backgroundColor: Theme.of( + context, + ).colorScheme.errorContainer, content: Text( error.toString(), style: .new( diff --git a/lib/widgets/lazy_loading/message_avatar.dart b/lib/widgets/lazy_loading/message_avatar.dart index 709d558..93c2554 100644 --- a/lib/widgets/lazy_loading/message_avatar.dart +++ b/lib/widgets/lazy_loading/message_avatar.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/author.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; @@ -6,11 +6,11 @@ import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/models/event.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; -class const MessageAvatar( - final Event event, { - final double height = 24, - super.key, -}) extends ConsumerWidget { +class MessageAvatar extends ConsumerWidget { + final Event event; + final double height; + const MessageAvatar(this.event, {this.height = 24, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => switch (ref.watch( AuthorController.provider(event), diff --git a/lib/widgets/lazy_loading/message_displayname.dart b/lib/widgets/lazy_loading/message_displayname.dart index 9203f69..bd5733c 100644 --- a/lib/widgets/lazy_loading/message_displayname.dart +++ b/lib/widgets/lazy_loading/message_displayname.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/author.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; @@ -6,12 +6,17 @@ import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/string_to_color.dart"; import "package:nexus/models/event.dart"; -class const MessageDisplayname( - final Event event, { - final TextStyle? style, - final bool clickable = true, - super.key, -}) extends ConsumerWidget { +class MessageDisplayname extends ConsumerWidget { + final Event event; + final TextStyle? style; + final bool clickable; + const MessageDisplayname( + this.event, { + this.clickable = true, + this.style, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) => switch (ref.watch( AuthorController.provider(event), @@ -35,11 +40,14 @@ class const MessageDisplayname( maxLines: 1, overflow: .ellipsis, ), + if (event.pmp != null) Text( "(via ${event.sender})", - style: Theme.of(context).textTheme.labelSmall - ?.copyWith(color: event.sender.colorHash, fontWeight: .bold), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: event.sender.colorHash, + fontWeight: .bold, + ), maxLines: 1, overflow: .ellipsis, ), diff --git a/lib/widgets/linkified_text.dart b/lib/widgets/linkified_text.dart index 77ba7a4..653248c 100644 --- a/lib/widgets/linkified_text.dart +++ b/lib/widgets/linkified_text.dart @@ -1,14 +1,14 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_linkify/flutter_linkify.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/helpers/launch_helper.dart"; -final class const LinkifiedText( - final String text, { - final int? maxLines, - final TextStyle? style, - super.key, -}) extends ConsumerWidget { +class LinkifiedText extends ConsumerWidget { + final String text; + final int? maxLines; + final TextStyle? style; + const LinkifiedText(this.text, {this.maxLines, this.style, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => Linkify( text: text, diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart index 180cd1b..fc84563 100644 --- a/lib/widgets/loading.dart +++ b/lib/widgets/loading.dart @@ -1,6 +1,9 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class Loading extends StatelessWidget { + final double? height; + const Loading({this.height, super.key}); -class const Loading({super.key, final double? height}) extends StatelessWidget { @override Widget build(BuildContext context) => Center( child: Padding( diff --git a/lib/widgets/member_list.dart b/lib/widgets/member_list.dart index 9ddafa3..92bb179 100644 --- a/lib/widgets/member_list.dart +++ b/lib/widgets/member_list.dart @@ -1,5 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_buttons/m3e_buttons.dart"; @@ -16,8 +16,10 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/user_bottom_sheet.dart"; -class const MemberList(final String roomId, {super.key}) - extends HookConsumerWidget { +class MemberList extends HookConsumerWidget { + final String roomId; + const MemberList(this.roomId, {super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final statusIndex = useState(0); @@ -94,9 +96,9 @@ class const MemberList(final String roomId, {super.key}) ), SliverM3ECardList( padding: .all(4), - color: Theme.of(context) - .colorScheme - .surfaceContainerHigh, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHigh, margin: .symmetric(horizontal: 12, vertical: 4), itemCount: members.length, itemBuilder: (context, index) => diff --git a/lib/widgets/message_image.dart b/lib/widgets/message_image.dart index b04c209..914a252 100644 --- a/lib/widgets/message_image.dart +++ b/lib/widgets/message_image.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_blurhash/flutter_blurhash.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/helpers/mxc_image.dart"; @@ -7,12 +7,12 @@ import "package:nexus/models/requests/download_media.dart"; import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/loading.dart"; -final class const MessageImage( - final Uri url, { - final i.ImageInfo? info, - required final bool encrypted, - super.key, -}) extends ConsumerWidget { +class MessageImage extends ConsumerWidget { + final Uri url; + final i.ImageInfo? info; + final bool encrypted; + const MessageImage(this.url, {this.info, required this.encrypted, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final request = DownloadMediaRequest(mxc: url, encrypted: encrypted); diff --git a/lib/widgets/pages/notifications.dart b/lib/widgets/pages/notifications.dart deleted file mode 100644 index 2ad8930..0000000 --- a/lib/widgets/pages/notifications.dart +++ /dev/null @@ -1,215 +0,0 @@ -import "dart:async"; - -import "package:collection/collection.dart"; -import "package:m3e_buttons/m3e_buttons.dart"; -import "package:material_ui/material_ui.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/jump_to_event.dart"; -import "package:nexus/controllers/key.dart"; -import "package:nexus/controllers/notifications.dart"; -import "package:nexus/controllers/spaces.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/widgets/appbar.dart"; -import "package:nexus/widgets/error_dialog.dart"; -import "package:nexus/widgets/highlight_wrapper.dart"; -import "package:nexus/widgets/loading.dart"; -import "package:nexus/widgets/renderers/event.dart"; -import "package:super_sliver_list/super_sliver_list.dart"; - -class const NotificationsPage({ - final String? highlightedEventId, - final bool defaultToAllNotifications = false, - super.key, -}) extends HookConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final options = { - M3EToggleButtonGroupAction( - checkedLabel: Text("Mentions"), - icon: Icon(Icons.alternate_email), - ): .highlight, - M3EToggleButtonGroupAction( - checkedLabel: Text("All Notifications"), - icon: Icon(Icons.notifications), - ): .notify, - }; - final unreadTypeIndex = useState(defaultToAllNotifications ? 1 : 0); - - final highlightedId = useState(highlightedEventId); - final listController = useRef(ListController()); - final scrollController = useScrollController(); - - final provider = NotificationsController.provider(( - options.values.toList()[unreadTypeIndex.value], - null, - )); - final notifications = ref.watch(provider); - final notifier = ref.watch(provider.notifier); - - useEffect(() { - if (highlightedId.value == null) return null; - Timer? timer; - - void listener() => - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (!listController.value.isAttached) return; - - final notifications = await ref.watch(provider.future); - - final index = notifications.indexWhere( - (element) => element.eventId == highlightedId.value!, - ); - - if (index == -1) return; - - listController.value.animateToItem( - index: index, - scrollController: scrollController, - alignment: 0.5, - duration: (_) => .new(milliseconds: 700), - curve: (_) => Curves.easeInOut, - ); - timer = Timer(.new(seconds: 1), () { - highlightedId.value = null; - }); - listController.value.removeListener(listener); - }); - - listController.value.addListener(listener); - return timer?.cancel; - }, []); - - useEffect(() { - Future listener() async { - if (!scrollController.hasClients || notifications.isLoading) return; - - if (scrollController.position.pixels >= - scrollController.position.maxScrollExtent) { - await notifier.loadOlder(); - } - } - - scrollController.addListener(listener); - return () => scrollController.removeListener(listener); - }, [scrollController, notifications]); - - return Scaffold( - appBar: Appbar(title: Text("Notifications")), - body: Stack( - children: [ - Column( - children: [ - if (notifications is AsyncLoading && notifications.value != null) - const LinearProgressIndicator(minHeight: 2), - Expanded( - child: switch (notifications) { - AsyncData(:final value) || AsyncLoading(:final value?) => - value.isEmpty - ? Center( - child: Text( - "No notifications yet", - style: Theme.of(context).textTheme.headlineMedium, - ), - ) - : SuperListView.builder( - listController: listController.value, - controller: scrollController, - itemCount: value.length, - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 12, - ), - reverse: true, - itemBuilder: (context, index) { - final event = value[index]; - final isHighlighted = - event.eventId == highlightedId.value; - - return Padding( - padding: .only(top: 8), - child: HighlightWrapper( - InkWell( - onTap: () async { - final spaces = ref.read( - SpacesController.provider, - ); - final space = spaces.firstWhereOrNull( - (space) => - space.children.any( - (room) => - room.metadata?.id == - event.roomId, - ) || - space.subSpaces.any( - (subSpace) => - subSpace.children.any( - (room) => - room.metadata?.id == - event.roomId, - ), - ), - ); - if (space == null) return; - - await ref - .read( - KeyController.provider( - KeyController.spaceKey, - ).notifier, - ) - .set(space.id); - await ref - .read( - KeyController.provider( - KeyController.roomKey, - ).notifier, - ) - .set(event.roomId); - ref - .watch( - JumpToEventController.provider( - event.roomId, - ).notifier, - ) - .set(event.eventId); - - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - child: IgnorePointer( - child: EventRenderer(event), - ), - ), - isHighlighted: isHighlighted, - ), - ); - }, - ), - AsyncLoading() => const Loading(), - AsyncError(:final error, :final stackTrace) => ErrorDialog( - error, - stackTrace, - ), - }, - ), - ], - ), - Align( - alignment: .topRight, - child: Padding( - padding: .all(16), - child: M3EToggleButtonGroup( - selectedIndex: unreadTypeIndex.value, - onSelectedIndexChanged: (index) => - unreadTypeIndex.value = index ?? unreadTypeIndex.value, - actions: options.keys.toList(), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/widgets/pinned_events_drawer.dart b/lib/widgets/pinned_events_drawer.dart index 73d9a79..7cb9fe3 100644 --- a/lib/widgets/pinned_events_drawer.dart +++ b/lib/widgets/pinned_events_drawer.dart @@ -1,5 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/pinned_events.dart"; import "package:nexus/models/event.dart"; @@ -7,12 +7,17 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/renderers/event.dart"; -final class const PinnedEventsDrawer( - final String roomId, { - required final IList Function(Event event) getEventOptions, - required final Future Function(String eventId) jumpToId, - super.key, -}) extends HookConsumerWidget { +class PinnedEventsDrawer extends HookConsumerWidget { + final String roomId; + final IList Function(Event event) getEventOptions; + final Future Function(String eventId) jumpToId; + const PinnedEventsDrawer( + this.roomId, { + required this.getEventOptions, + required this.jumpToId, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final pinsProvider = ref.watch(PinnedEventsController.provider(roomId)); @@ -66,13 +71,11 @@ final class const PinnedEventsDrawer( }, child: Padding( padding: .symmetric(vertical: 4), - child: IgnorePointer( - child: EventRenderer( - event, - maxLines: 2, - isGrouped: false, - getEventOptions: getEventOptions, - ), + child: EventRenderer( + event, + maxLines: 2, + isGrouped: false, + getEventOptions: getEventOptions, ), ), ); diff --git a/lib/widgets/players/audio.dart b/lib/widgets/players/audio.dart index bd50a8b..22a91f7 100644 --- a/lib/widgets/players/audio.dart +++ b/lib/widgets/players/audio.dart @@ -1,18 +1,18 @@ import "dart:async"; - -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:media_kit/media_kit.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/audio.dart"; -class const AudioPlayer( - final Uri uri, - final AudioInfo? info, { - final bool encrypted = false, - super.key, -}) extends HookConsumerWidget { +class AudioPlayer extends HookConsumerWidget { + final Uri uri; + final AudioInfo? info; + final bool encrypted; + + const AudioPlayer(this.uri, this.info, {this.encrypted = false, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final player = useMemoized( diff --git a/lib/widgets/players/video.dart b/lib/widgets/players/video.dart index 024c30e..457cdc4 100644 --- a/lib/widgets/players/video.dart +++ b/lib/widgets/players/video.dart @@ -1,6 +1,5 @@ import "dart:async"; - -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/video.dart"; @@ -8,12 +7,12 @@ import "package:flutter_hooks/flutter_hooks.dart"; import "package:media_kit/media_kit.dart"; import "package:media_kit_video/media_kit_video.dart"; -class const VideoPlayer( - final Uri uri, - final VideoInfo? info, { - final bool encrypted = false, - super.key, -}) extends HookConsumerWidget { +class VideoPlayer extends HookConsumerWidget { + final VideoInfo? info; + final Uri uri; + final bool encrypted; + const VideoPlayer(this.uri, this.info, {this.encrypted = false, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final player = useMemoized( diff --git a/lib/widgets/reaction_row.dart b/lib/widgets/reaction_row.dart index 195aa4f..b2fa5ca 100644 --- a/lib/widgets/reaction_row.dart +++ b/lib/widgets/reaction_row.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state.dart"; @@ -10,7 +10,10 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/main.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -class const ReactionRow(final Event event, {super.key}) extends ConsumerWidget { +class ReactionRow extends ConsumerWidget { + final Event event; + const ReactionRow(this.event, {super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final clientState = ref.watch(ClientStateController.provider); @@ -64,10 +67,9 @@ class const ReactionRow(final Event event, {super.key}) extends ConsumerWidget { enabled.value = false; try { final controller = ref.watch( - RoomChatController.provider(( + RoomChatController.provider( event.roomId, - null, - )).notifier, + ).notifier, ); if (selected) { diff --git a/lib/widgets/renderers/event.dart b/lib/widgets/renderers/event.dart index 697161f..a9c703a 100644 --- a/lib/widgets/renderers/event.dart +++ b/lib/widgets/renderers/event.dart @@ -1,6 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/gestures.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/helpers/extensions/show_context_menu.dart"; @@ -25,17 +24,24 @@ import "package:nexus/widgets/renderers/message.dart"; import "package:nexus/widgets/reaction_row.dart"; import "package:nexus/widgets/renderers/membership.dart"; import "package:nexus/widgets/renderers/generic_event.dart"; -import "package:nexus/widgets/timestamp.dart"; -class const EventRenderer( - final Event event, { - final bool textOnly = false, - final bool isGrouped = false, - final int? maxLines, - final VoidCallback? onTapReply, - final IList Function(Event event)? getEventOptions, - super.key, -}) extends HookConsumerWidget { +class EventRenderer extends HookConsumerWidget { + final Event event; + final bool textOnly; + final bool isGrouped; + final int? maxLines; + final VoidCallback? onTapReply; + final IList Function(Event event)? getEventOptions; + const EventRenderer( + this.event, { + this.onTapReply, + this.textOnly = false, + this.isGrouped = false, + this.maxLines, + this.getEventOptions, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -153,7 +159,7 @@ class const EventRenderer( final contextMenuCallback = getEventOptions == null ? null - : (PositionedGestureDetails details) => context.showContextMenu( + : (details) => context.showContextMenu( globalPosition: details.globalPosition, children: getEventOptions!(event).toList(), ); @@ -196,18 +202,10 @@ class const EventRenderer( onSecondaryTapUp: contextMenuCallback, onLongPressStart: contextMenuCallback, child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8) - .copyWith(top: isGrouped ? 0 : 8), - child: Wrap( - verticalDirection: .up, - spacing: 6, - crossAxisAlignment: .center, - children: [ - child, - if (child is! MessageRenderer) - Timestamp(event.timestamp), - ], - ), + padding: EdgeInsets.symmetric( + horizontal: 8, + ).copyWith(top: isGrouped ? 0 : 8), + child: child, ), ), ), @@ -215,7 +213,10 @@ class const EventRenderer( ), ...[ - if (child is! MessageRenderer) ReactionRow(event), + if (event.content is! MessageContent && + event.content is! StickerContent && + event.content is! EncryptedContent) + ReactionRow(event), if (event.sendError != null && event.sendError != "not sent") Padding( diff --git a/lib/widgets/renderers/generic_event.dart b/lib/widgets/renderers/generic_event.dart index eb5b29b..6dfae53 100644 --- a/lib/widgets/renderers/generic_event.dart +++ b/lib/widgets/renderers/generic_event.dart @@ -1,19 +1,18 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; + +class GenericEventRenderer extends StatelessWidget { + final IconData icon; + final List children; + const GenericEventRenderer(this.icon, this.children, {super.key}); -class const GenericEventRenderer( - final IconData icon, - final List children, { - super.key, -}) extends StatelessWidget { @override Widget build(BuildContext context) => Padding( padding: .symmetric(vertical: 4), child: Row( spacing: 8, - mainAxisSize: .min, children: [ Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)), - Flexible(child: Wrap(spacing: 4, children: children)), + Expanded(child: Wrap(spacing: 4, children: children)), ], ), ); diff --git a/lib/widgets/renderers/membership.dart b/lib/widgets/renderers/membership.dart index 5ce5d68..b2835b4 100644 --- a/lib/widgets/renderers/membership.dart +++ b/lib/widgets/renderers/membership.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/string_to_color.dart"; @@ -7,8 +7,10 @@ import "package:nexus/models/event.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/renderers/generic_event.dart"; -class const MembershipRenderer(final Event event, {super.key}) - extends StatelessWidget { +class MembershipRenderer extends StatelessWidget { + final Event event; + const MembershipRenderer(this.event, {super.key}); + @override Widget build(BuildContext context) { assert( diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index a0bbf99..c684085 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -1,6 +1,5 @@ import "package:collection/collection.dart"; -import "package:flutter/rendering.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:linkify/linkify.dart"; import "package:nexus/controllers/client_state.dart"; @@ -16,136 +15,48 @@ import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/linkified_text.dart"; import "package:nexus/widgets/message_image.dart"; import "package:nexus/widgets/reaction_row.dart"; -import "package:nexus/widgets/timestamp.dart"; import "package:nexus/widgets/url_preview.dart"; +import "package:timeago/timeago.dart"; import "package:nexus/widgets/event_preview.dart"; import "package:nexus/widgets/players/video.dart"; import "package:nexus/widgets/players/audio.dart"; -class const MessageRenderer( - final Event event, { - final VoidCallback? onTapReply, - final bool textOnly = false, - final bool isGrouped = false, - final int? maxLines, - super.key, -}) extends ConsumerWidget { +class MessageRenderer extends ConsumerWidget { + final Event event; + final bool textOnly; + final bool isGrouped; + final int? maxLines; + final VoidCallback? onTapReply; + const MessageRenderer( + this.event, { + this.onTapReply, + this.textOnly = false, + this.isGrouped = false, + this.maxLines, + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final errorStyle = TextStyle(color: colorScheme.error); + final timestamp = Tooltip( + message: event.timestamp.toString(), + child: Text( + format(event.timestamp), + maxLines: 1, + overflow: .ellipsis, + style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey), + ), + ); + final textStyle = TextStyle( fontSize: event.localContent?.bigEmoji == true ? 32 : null, fontStyle: event.content is EmoteMessageContent ? .italic : null, ); - final rendered = switch (event.content) { - EncryptedContent() => Text("Unable to decrypt event", style: errorStyle), - StickerContent(:final body, :final url, :final info) => - textOnly - ? Text(body, maxLines: maxLines, overflow: .ellipsis) - : ConstrainedBox( - constraints: .loose(.square(200)), - child: MessageImage(url, info: info, encrypted: false), - ), - // TODO: Handle locations - // LocationMessageContent(:final body , :final geoUri) => - TextMessageContent(:final body, :final formattedBody, :final format) || - NoticeMessageContent(:final body, :final formattedBody, :final format) || - EmoteMessageContent(:final body, :final formattedBody, :final format) || - ImageMessageContent(:final body, :final formattedBody, :final format) || - VideoMessageContent(:final body, :final formattedBody, :final format) || - AudioMessageContent(:final body, :final formattedBody, :final format) || - FileMessageContent( - :final body, - :final formattedBody, - :final format, - ) => Column( - crossAxisAlignment: .start, - children: [ - format == .html && !textOnly - ? Html( - roomId: event.roomId, - textStyle: textStyle, - formattedBody!.replaceAllMapped( - RegExp( - r"(]*>.*?<\/a>)|(\bhttps?:\/\/[^\s<]+)", - caseSensitive: false, - dotAll: true, - ), - (m) { - // If it's already an tag, leave it unchanged - if (m.group(1) != null) { - return m.group(1)!; - } - - // Otherwise, wrap the bare URL - final url = m.group(2)!; - return "$url"; - }, - ), - ) - : LinkifiedText(body, style: textStyle, maxLines: maxLines), - - if (!textOnly) ...[ - if (event.content - case ImageMessageContent(:final url) || - FileMessageContent(:final url) || - VideoMessageContent(:final url) || - AudioMessageContent(:final url)) - ConstrainedBox( - constraints: .loose(.square(500)), - child: switch (event.content) { - VideoMessageContent(:final info, :final file) => VideoPlayer( - url, - info, - encrypted: file != null, - ), - AudioMessageContent(:final info, :final file) => AudioPlayer( - url, - info, - - encrypted: file != null, - ), - FileMessageContent(:final info, :final filename) => FileCard( - url, - info, - filename: filename, - ), - ImageMessageContent(:final info, :final file) => MessageImage( - url, - info: info, - encrypted: file != null, - ), - _ => SizedBox.shrink(), - }, - ), - - if (event.lastEditRowId != 0) - Text("(edited)", style: theme.textTheme.labelSmall), - - if (linkify(body) - .firstWhereOrNull((element) => element is UrlElement) - case final UrlElement link?) - if (Uri.tryParse(link.url) case final Uri url?) UrlPreview(url), - ], - ], - ), - MessageContent(:final body) => - body == null - ? Text("This message is redacted", style: errorStyle) - : Wrap( - spacing: 8, - children: [ - Text("Unknown message type:", style: errorStyle), - Text(body), - ], - ), - _ => throw Exception("This is impossible"), - }; - return Row( crossAxisAlignment: .start, mainAxisSize: .min, @@ -166,7 +77,7 @@ class const MessageRenderer( spacing: 4, children: [ Flexible(child: MessageDisplayname(event)), - Flexible(flex: 0, child: Timestamp(event.timestamp)), + Flexible(flex: 0, child: timestamp), ], ), Card( @@ -219,13 +130,172 @@ class const MessageRenderer( ), ), ), + switch (event.content) { + EncryptedContent() => Text( + "Unable to decrypt event", + style: errorStyle, + ), + StickerContent(:final body, :final url, :final info) => + textOnly + ? Text( + body, + maxLines: maxLines, + overflow: .ellipsis, + ) + : ConstrainedBox( + constraints: .loose(.square(200)), + child: MessageImage( + url, + info: info, + encrypted: false, + ), + ), + // TODO: Handle locations + // LocationMessageContent(:final body , :final geoUri) => + TextMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + NoticeMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + EmoteMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + ImageMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + VideoMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + AudioMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + FileMessageContent( + :final body, + :final formattedBody, + :final format, + ) => Column( + crossAxisAlignment: .start, + children: [ + format == .html && !textOnly + ? Html( + roomId: event.roomId, + textStyle: textStyle, + formattedBody!.replaceAllMapped( + RegExp( + r"(]*>.*?<\/a>)|(\bhttps?:\/\/[^\s<]+)", + caseSensitive: false, + dotAll: true, + ), + (m) { + // If it's already an tag, leave it unchanged + if (m.group(1) != null) { + return m.group(1)!; + } - RendererBinding.instance.mouseTracker.mouseIsConnected - ? SelectableRegion( - selectionControls: materialTextSelectionControls, - child: rendered, - ) - : rendered, + // Otherwise, wrap the bare URL + final url = m.group(2)!; + return "$url"; + }, + ), + ) + : LinkifiedText( + body, + style: textStyle, + maxLines: maxLines, + ), + + if (!textOnly) ...[ + if (event.content + case ImageMessageContent(:final url) || + FileMessageContent(:final url) || + VideoMessageContent(:final url) || + AudioMessageContent(:final url)) + ConstrainedBox( + constraints: .loose(.square(500)), + child: switch (event.content) { + VideoMessageContent( + :final info, + :final file, + ) => + VideoPlayer( + url, + info, + encrypted: file != null, + ), + AudioMessageContent( + :final info, + :final file, + ) => + AudioPlayer( + url, + info, + + encrypted: file != null, + ), + FileMessageContent( + :final info, + :final filename, + ) => + FileCard(url, info, filename: filename), + ImageMessageContent( + :final info, + :final file, + ) => + MessageImage( + url, + info: info, + encrypted: file != null, + ), + _ => SizedBox.shrink(), + }, + ), + + if (event.lastEditRowId != 0) + Text( + "(edited)", + style: theme.textTheme.labelSmall, + ), + + if (linkify(body).firstWhereOrNull( + (element) => element is UrlElement, + ) + case final UrlElement link?) + if (Uri.tryParse(link.url) case final Uri url?) + UrlPreview(url), + ], + ], + ), + MessageContent(:final body) => + body == null + ? Text( + "This message is redacted", + style: errorStyle, + ) + : Wrap( + spacing: 8, + children: [ + Text( + "Unknown message type:", + style: errorStyle, + ), + Text(body), + ], + ), + _ => throw Exception("This is impossible"), + }, if (!textOnly) ReactionRow(event), ], ), diff --git a/lib/widgets/room_appbar.dart b/lib/widgets/room_appbar.dart index 305c490..93eca85 100644 --- a/lib/widgets/room_appbar.dart +++ b/lib/widgets/room_appbar.dart @@ -1,4 +1,4 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/widgets/appbar.dart"; @@ -7,14 +7,21 @@ import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/linkified_text.dart"; import "package:nexus/widgets/room_menu.dart"; -final class const RoomAppbar({ - required final String? roomId, - required final bool isDesktop, - required final void Function() onOpenDrawer, - final void Function(BuildContext context)? onOpenMemberList, - final void Function()? onOpenPinnedMessagesList, - super.key, -}) extends ConsumerWidget implements PreferredSizeWidget { +class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget { + final bool isDesktop; + final void Function(BuildContext context)? onOpenMemberList; + final void Function()? onOpenPinnedMessagesList; + final void Function() onOpenDrawer; + final String? roomId; + const RoomAppbar({ + required this.roomId, + required this.isDesktop, + required this.onOpenDrawer, + this.onOpenMemberList, + this.onOpenPinnedMessagesList, + super.key, + }); + @override Size get preferredSize => AppBar().preferredSize; @@ -33,52 +40,48 @@ final class const RoomAppbar({ constraints: .loose(.fromWidth(400)), child: Padding( padding: .all(24), - child: SingleChildScrollView( - child: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - spacing: 8, - children: [ - Row( - spacing: 12, - mainAxisSize: .min, - children: [ - if (room.metadata?.avatar != null) - ExpandableImage( - room.metadata?.avatar == null - ? null - : .new(mxc: room.metadata!.avatar!), - child: AvatarOrHash( - room.metadata?.avatar, - room.metadata?.name ?? "Unnamed Room", - height: 64, - fallback: Icon(Icons.numbers), - ), - ), - Expanded( - child: Text( + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + spacing: 8, + children: [ + Row( + spacing: 12, + mainAxisSize: .min, + children: [ + if (room.metadata?.avatar != null) + ExpandableImage( + room.metadata?.avatar == null + ? null + : .new(mxc: room.metadata!.avatar!), + child: AvatarOrHash( + room.metadata?.avatar, room.metadata?.name ?? "Unnamed Room", - overflow: .ellipsis, - maxLines: 3, - style: Theme.of(context) - .textTheme - .headlineSmall, + height: 64, + fallback: Icon(Icons.numbers), ), ), - ], - ), - if (room.metadata?.topic?.isNotEmpty == true) - LinkifiedText( - room.metadata!.topic!, - style: Theme.of(context).textTheme.bodyLarge - ?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), + Expanded( + child: Text( + room.metadata?.name ?? "Unnamed Room", + overflow: .ellipsis, + maxLines: 3, + style: Theme.of(context).textTheme.headlineSmall, + ), ), - ], - ), + ], + ), + if (room.metadata?.topic?.isNotEmpty == true) + LinkifiedText( + room.metadata!.topic!, + style: Theme.of(context).textTheme.bodyLarge + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ), + ], ), ), ), diff --git a/lib/widgets/room_chat.dart b/lib/widgets/room_chat.dart new file mode 100644 index 0000000..cb1e695 --- /dev/null +++ b/lib/widgets/room_chat.dart @@ -0,0 +1,564 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:measure_size/measure_size.dart"; +import "package:nexus/controllers/account_data.dart"; +import "package:nexus/controllers/client.dart"; +import "package:nexus/controllers/client_state.dart"; +import "package:nexus/controllers/member_list_opened.dart"; +import "package:nexus/controllers/pinned_ids.dart"; +import "package:nexus/controllers/power_level.dart"; +import "package:nexus/controllers/rooms.dart"; +import "package:nexus/controllers/room_chat.dart"; +import "package:nexus/controllers/via.dart"; +import "package:nexus/models/content/message.dart"; +import "package:nexus/models/event.dart"; +import "package:nexus/models/relation_type.dart"; +import "package:nexus/widgets/composer/composer.dart"; +import "package:nexus/widgets/emoji_picker_button.dart"; +import "package:nexus/widgets/pinned_events_drawer.dart"; +import "package:nexus/widgets/renderers/event.dart"; +import "package:nexus/widgets/member_list.dart"; +import "package:nexus/widgets/room_appbar.dart"; +import "package:nexus/widgets/highlight_wrapper.dart"; +import "package:nexus/widgets/error_dialog.dart"; +import "package:nexus/main.dart"; +import "package:nexus/widgets/loading.dart"; +import "package:super_sliver_list/super_sliver_list.dart"; + +class RoomChat extends HookConsumerWidget { + final bool isDesktop; + final bool showMembersByDefault; + final String? roomId; + const RoomChat({ + required this.roomId, + required this.isDesktop, + required this.showMembersByDefault, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final relatedEvent = useState(null); + final relationType = useState(RelationType.reply); + final highlightedEvent = useState(null); + + final composerSize = useState(64); + + final userId = ref.watch(ClientStateController.provider)?.userId; + final memberListOpened = ref.watch(MemberListOpenedController.provider); + final theme = Theme.of(context); + + final nothing = Center( + child: Text( + "Nothing to see here...", + style: theme.textTheme.headlineMedium, + ), + ); + if (userId == null || this.roomId == null) { + return Scaffold( + appBar: RoomAppbar( + roomId: this.roomId, + isDesktop: isDesktop, + onOpenDrawer: () => Scaffold.of(context).openDrawer(), + ), + body: nothing, + ); + } + + final roomId = this.roomId!; + + final controllerProvider = RoomChatController.provider(roomId); + final notifier = ref.watch(controllerProvider.notifier); + + final client = ref.watch(ClientController.provider.notifier); + + final listController = useRef(ListController()); + final scrollController = useScrollController(); + final controllerData = ref.watch(controllerProvider); + + final topEventBeforeLoad = useState(null); + + Future jumpToId(String eventId) async { + final index = controllerData.value?.indexWhere( + (element) => element.eventId == eventId, + ); + if (index == null) return; + + listController.value.animateToItem( + index: index, + scrollController: scrollController, + alignment: 0.5, + duration: (_) => .new(milliseconds: 700), + curve: (_) => Curves.easeInOut, + ); + highlightedEvent.value = eventId; + await Future.delayed(.new(seconds: 1), () { + if (highlightedEvent.value == eventId) { + highlightedEvent.value = null; + } + }); + } + + Future loadOlder() async { + if (controllerData case AsyncData(:final value?)) { + topEventBeforeLoad.value = value.firstOrNull?.eventId; + await notifier.loadOlder(); + } + } + + useEffect(() { + ref + .read(controllerProvider.future) + .then( + (_) => WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients) { + scrollController.jumpTo( + scrollController.position.maxScrollExtent - .000001, + ); + } + }), + ); + + return null; + }, [scrollController.hasClients]); + + useEffect(() { + if (controllerData case AsyncData( + :final value?, + ) when scrollController.hasClients) { + if (topEventBeforeLoad.value != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients) { + final index = value.indexWhere( + (event) => event.eventId == topEventBeforeLoad.value, + ); + if (index != -1) { + listController.value.jumpToItem( + index: index, + scrollController: scrollController, + alignment: 0, + ); + } + } + topEventBeforeLoad.value = null; + }); + } else if (scrollController.position.atEdge && + scrollController.position.pixels != 0) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients) { + scrollController.jumpTo( + scrollController.position.maxScrollExtent, + ); + } + }); + } + } + + return null; + }, [controllerData]); + + useEffect(() { + Future listener() async { + if (!scrollController.hasClients || !scrollController.position.atEdge) { + return; + } + + final room = ref.watch( + RoomsController.provider.select((value) => value[roomId]), + ); + if (room == null) return; + + if (scrollController.position.pixels == 0) { + if (room.hasMore) { + await loadOlder(); + } + } else { + await client.markRead(room); + } + } + + scrollController.addListener(listener); + return () => scrollController.removeListener(listener); + }, [roomId, controllerData]); + + final composerNode = useFocusNode( + onKeyEvent: (_, event) { + if (event is KeyDownEvent && event.logicalKey == .escape) { + relatedEvent.value = null; + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + }, + ); + + IList getEventOptions(Event event) { + final danger = theme.colorScheme.error; + final isSentByMe = event.sender == userId; + + return [ + if (ref.watch( + PowerLevelController.provider( + .new(eventType: .reaction, roomId: roomId), + ), + )) + PopupMenuItem( + enabled: false, + child: IconTheme( + data: theme.iconTheme, + child: Row( + children: [ + ...{ + ...ref.watch( + AccountDataController.provider.select( + (value) => value.recentEmoji + .map((entry) => entry.emoji) + .toIList(), + ), + ), + "👍", + "🤣", + "😭", + "🤔", + } + .toIList() + .sublist(0, 4) + .map( + (emoji) => IconButton( + onPressed: () async { + Navigator.of(context).pop(); + await notifier + .sendReaction(emoji, event) + .onError(showError); + }, + icon: Text(emoji), + ), + ), + EmojiPickerButton( + context: context, + onPressed: Navigator.of(context).pop, + onSelection: (emoji) => + notifier.sendReaction(emoji, event).onError(showError), + ), + ], + ), + ), + ), + if (ref.watch( + PowerLevelController.provider( + .new(eventType: .message, roomId: roomId), + ), + )) + PopupMenuItem( + onTap: () { + relatedEvent.value = event; + relationType.value = .reply; + composerNode.requestFocus(); + }, + child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")), + ), + if (event.content is MessageContent && isSentByMe) + PopupMenuItem( + onTap: () { + relatedEvent.value = event; + relationType.value = .edit; + composerNode.requestFocus(); + }, + child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")), + ), + if (ref.watch( + PowerLevelController.provider( + .state(eventType: .pinnedEvents, roomId: roomId), + ), + )) + switch (ref + .watch(PinnedIdsController.provider(roomId)) + .contains(event.eventId)) { + bool isPinned => PopupMenuItem( + onTap: () async { + try { + final notifier = ref.read( + PinnedIdsController.provider(roomId).notifier, + ); + if (isPinned) { + await notifier.removePin(event.eventId); + } else { + await notifier.addPin(event.eventId); + } + } catch (error, stackTrace) { + showError(error, stackTrace); + } + }, + child: ListTile( + leading: Icon(Icons.push_pin), + title: Text(isPinned == true ? "Unpin Event" : "Pin Event"), + ), + ), + }, + PopupMenuItem( + onTap: () async { + final room = ref.watch( + RoomsController.provider.select((value) => value[roomId]), + ); + if (room == null) return; + + final vias = ref.watch(ViaController.provider(room)); + + await Clipboard.setData( + ClipboardData( + text: + "matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias)", + ), + ); + }, + child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")), + ), + if (ref.watch( + PowerLevelController.provider( + .redaction(targetUser: event.sender, roomId: roomId), + ), + )) + PopupMenuItem( + onTap: () => showDialog( + context: context, + builder: (context) => HookBuilder( + builder: (_) { + final deleteReasonController = useTextEditingController(); + return AlertDialog( + title: Text("Delete Message"), + content: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + "Are you sure you want to delete this message? This can not be reversed.", + ), + SizedBox(height: 12), + TextField( + controller: deleteReasonController, + textCapitalization: .sentences, + decoration: .new( + labelText: "Reason for deletion (optional)", + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text("Cancel"), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await notifier + .deleteMessage( + event, + reason: deleteReasonController.text, + ) + .onError(showError); + }, + child: Text("Delete"), + ), + ], + ); + }, + ), + ), + child: ListTile( + leading: Icon(Icons.delete, color: danger), + title: Text("Delete", style: .new(color: danger)), + ), + ), + PopupMenuItem( + onTap: () => showDialog( + context: context, + builder: (context) => HookBuilder( + builder: (_) { + final reasonController = useTextEditingController(); + return AlertDialog( + title: Text("Report"), + content: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + "Report this event to your server administrators, who can take action like banning this server or room.", + ), + + SizedBox(height: 12), + TextField( + controller: reasonController, + textCapitalization: .sentences, + decoration: .new( + labelText: "Reason for report (optional)", + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text("Cancel"), + ), + TextButton( + onPressed: () { + client.reportEvent( + .new( + roomId: roomId, + eventId: event.eventId, + reason: reasonController.text.isEmpty + ? null + : reasonController.text, + ), + ); + Navigator.of(context).pop(); + }, + child: Text("Report"), + ), + ], + ); + }, + ), + ), + child: ListTile( + leading: Icon(Icons.report, color: danger), + title: Text("Report", style: .new(color: danger)), + ), + ), + ].toIList(); + } + + return Scaffold( + endDrawer: PinnedEventsDrawer( + roomId, + getEventOptions: getEventOptions, + jumpToId: jumpToId, + ), + body: Builder( + builder: (middleContext) => Scaffold( + endDrawer: showMembersByDefault ? null : MemberList(roomId), + appBar: RoomAppbar( + roomId: roomId, + isDesktop: isDesktop, + onOpenDrawer: Scaffold.of(context).openDrawer, + onOpenMemberList: (thisContext) { + ref + .watch(MemberListOpenedController.provider.notifier) + .set(!memberListOpened); + Scaffold.of(thisContext).openEndDrawer(); + }, + onOpenPinnedMessagesList: () { + Scaffold.of(middleContext).openEndDrawer(); + }, + ), + body: Row( + children: [ + Expanded( + child: Stack( + children: [ + Positioned.fill( + child: Padding( + padding: .symmetric(horizontal: 4), + child: switch (controllerData) { + AsyncData(:final value?) || + AsyncLoading(:final value?) => CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: .symmetric(vertical: 36), + child: Center( + child: ElevatedButton( + onPressed: controllerData is AsyncData + ? loadOlder + : null, + child: Text("Load More"), + ), + ), + ), + ), + + SuperSliverList.builder( + listController: listController.value, + itemCount: value.length, + itemBuilder: (_, index) { + final event = value[index]; + final previousEvent = value.getOrNull( + index - 1, + ); + return HighlightWrapper( + EventRenderer( + event, + onTapReply: () => + jumpToId(event.replyTo!), + getEventOptions: getEventOptions, + isGrouped: + previousEvent?.content + is MessageContent && + previousEvent?.redactedBy == null && + previousEvent?.relationType != + "m.replace" && + "${event.sender}${event.pmp?.id}" == + "${previousEvent?.sender}${previousEvent?.pmp?.id}", + ), + isHighlighted: + highlightedEvent.value == event.eventId, + ); + }, + ), + + SliverPadding( + padding: .only(bottom: composerSize.value), + ), + ], + ), + AsyncData() => nothing, + AsyncLoading() => Loading(), + AsyncError(:final error, :final stackTrace) => + ErrorDialog(error, stackTrace), + }, + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: MeasureSize( + onChange: (size) => composerSize.value = size.height, + child: Composer( + roomId, + node: composerNode, + onSend: + (text, {required shouldMention, required tags}) => + notifier + .send( + text, + tags: tags, + relationType: relationType.value, + shouldMention: shouldMention, + relation: relatedEvent.value, + ) + .onError(showError), + relationType: relationType.value, + relatedEvent: relatedEvent.value, + onDismiss: () => relatedEvent.value = null, + ), + ), + ), + ], + ), + ), + + if (memberListOpened == true && showMembersByDefault) + MemberList(roomId), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/room_chat/chat_timeline.dart b/lib/widgets/room_chat/chat_timeline.dart deleted file mode 100644 index 2cab4ca..0000000 --- a/lib/widgets/room_chat/chat_timeline.dart +++ /dev/null @@ -1,80 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; -import "package:nexus/helpers/hooks/chat_scroll.dart"; -import "package:nexus/models/content/message.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/widgets/renderers/event.dart"; -import "package:nexus/widgets/highlight_wrapper.dart"; -import "package:super_sliver_list/super_sliver_list.dart"; - -class const ChatTimeline({ - required final ChatScroll scroll, - required final Future Function(String) jumpToId, - required final IList Function(Event) getEventOptions, - required final String? highlightedEvent, - required final double composerHeight, - super.key, -}) extends StatelessWidget { - bool isGrouped(Event event, Event? previousEvent) => - previousEvent?.content is MessageContent && - previousEvent?.redactedBy == null && - previousEvent?.relationType != "m.replace" && - event.sender == previousEvent?.sender && - event.pmp?.id == previousEvent?.pmp?.id; - - Widget eventRow( - Event event, - Event? previousEvent, { - required Future Function(String) jumpToId, - required IList Function(Event) getEventOptions, - required String? highlightedEvent, - required Key key, - }) => HighlightWrapper( - EventRenderer( - event, - onTapReply: () => jumpToId(event.replyTo!), - getEventOptions: getEventOptions, - isGrouped: isGrouped(event, previousEvent), - ), - key: key, - isHighlighted: highlightedEvent == event.eventId, - ); - - @override - Widget build(BuildContext context) => CustomScrollView( - reverse: true, - center: scroll.centerKey, - keyboardDismissBehavior: .onDrag, - controller: scroll.scrollController, - slivers: [ - SliverToBoxAdapter(child: SizedBox(height: composerHeight)), - - SuperSliverList.builder( - itemCount: scroll.liveItems.length, - itemBuilder: (_, index) => eventRow( - scroll.liveItems[index], - index > 0 - ? scroll.liveItems.getOrNull(index - 1) - : scroll.historyItems.firstOrNull, - jumpToId: jumpToId, - getEventOptions: getEventOptions, - highlightedEvent: highlightedEvent, - key: scroll.keyFor(scroll.liveItems[index].eventId), - ), - ), - - SuperSliverList.builder( - key: scroll.centerKey, - itemCount: scroll.historyItems.length, - itemBuilder: (_, index) => eventRow( - scroll.historyItems[index], - scroll.historyItems.getOrNull(index + 1), - jumpToId: jumpToId, - getEventOptions: getEventOptions, - highlightedEvent: highlightedEvent, - key: scroll.keyFor(scroll.historyItems[index].eventId), - ), - ), - ], - ); -} diff --git a/lib/widgets/room_chat/room_chat.dart b/lib/widgets/room_chat/room_chat.dart deleted file mode 100644 index 709d22d..0000000 --- a/lib/widgets/room_chat/room_chat.dart +++ /dev/null @@ -1,231 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; -import "package:flutter/services.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:measure_size/measure_size.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/member_list_opened.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/controllers/room_chat.dart"; -import "package:nexus/helpers/hooks/chat_scroll.dart"; -import "package:nexus/models/event.dart"; -import "package:nexus/models/relation_type.dart"; -import "package:nexus/widgets/composer/composer.dart"; -import "package:nexus/widgets/pinned_events_drawer.dart"; -import "package:nexus/widgets/member_list.dart"; -import "package:nexus/widgets/room_appbar.dart"; -import "package:nexus/main.dart"; -import "package:nexus/widgets/room_chat/chat_timeline.dart"; -import "package:nexus/helpers/extensions/build_event_options.dart"; - -final class const RoomChat({ - required final String? roomId, - required final bool isDesktop, - required final bool showMembersByDefault, - final String? initialHighlightedEvent, - super.key, -}) extends HookConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final relatedEvent = useState(null); - final relationType = useState(RelationType.reply); - final contextualEvent = useState(initialHighlightedEvent); - final highlightedEvent = useState(null); - - final composerSize = useState(64); - - final userId = ref.watch(ClientStateController.provider)?.userId; - final memberListOpened = ref - .watch(MemberListOpenedController.provider) - .requireValue; - final theme = Theme.of(context); - - final nothing = Center( - child: Text( - "Nothing to see here...", - style: theme.textTheme.headlineMedium, - ), - ); - if (userId == null || this.roomId == null) { - return Scaffold( - appBar: RoomAppbar( - roomId: this.roomId, - isDesktop: isDesktop, - onOpenDrawer: () => Scaffold.of(context).openDrawer(), - ), - body: nothing, - ); - } - - final roomId = this.roomId!; - - final controllerProvider = RoomChatController.provider(( - roomId, - contextualEvent.value, - )); - final notifier = ref.watch(controllerProvider.notifier); - - final client = ref.read(ClientController.provider.notifier); - - final controllerData = ref.watch(controllerProvider); - - final scroll = ChatScroll.use( - controllerData: controllerData, - paginate: notifier.paginate, - contextualEvent: contextualEvent, - markRead: () async { - final room = ref.read( - RoomsController.provider.select((rooms) => rooms[roomId]), - ); - - if (room != null) { - await client.markRead(room); - } - }, - ); - - final composerNode = useFocusNode( - onKeyEvent: (_, event) { - if (event is KeyDownEvent && event.logicalKey == .escape) { - relatedEvent.value = null; - return .handled; - } - - return .ignored; - }, - ); - - Future jumpToId(String eventId) async { - highlightedEvent.value = eventId; - - await scroll.jumpToId(eventId); - await Future.delayed(.new(milliseconds: 700), () { - if (highlightedEvent.value == eventId) highlightedEvent.value = null; - }); - } - - useEffect(() { - if (initialHighlightedEvent == null) return null; - - void check() { - if (!context.mounted) return; - - if (scroll.scrollController.hasClients) { - jumpToId(initialHighlightedEvent!); - } else { - WidgetsBinding.instance.addPostFrameCallback((_) => check()); - } - } - - check(); - - return null; - }, [initialHighlightedEvent]); - - IList getEventOptions(Event event) => - event.buildEventOptions( - context: context, - ref: ref, - roomId: roomId, - userId: userId, - onRelation: (event, type) { - relatedEvent.value = event; - relationType.value = type; - composerNode.requestFocus(); - }, - ); - - return Scaffold( - endDrawer: PinnedEventsDrawer( - roomId, - getEventOptions: getEventOptions, - jumpToId: jumpToId, - ), - body: Builder( - builder: (middleContext) => Scaffold( - endDrawer: showMembersByDefault ? null : MemberList(roomId), - appBar: RoomAppbar( - roomId: roomId, - isDesktop: isDesktop, - onOpenDrawer: Scaffold.of(context).openDrawer, - onOpenMemberList: (thisContext) { - ref - .read(MemberListOpenedController.provider.notifier) - .set(!memberListOpened); - Scaffold.of(thisContext).openEndDrawer(); - }, - onOpenPinnedMessagesList: Scaffold.of(middleContext).openEndDrawer, - ), - body: Row( - children: [ - Expanded( - child: Stack( - children: [ - Positioned.fill( - child: Padding( - padding: .symmetric(horizontal: 4), - child: ChatTimeline( - scroll: scroll, - jumpToId: jumpToId, - getEventOptions: getEventOptions, - highlightedEvent: highlightedEvent.value, - composerHeight: composerSize.value, - ), - ), - ), - Positioned( - right: 16, - bottom: composerSize.value, - child: IgnorePointer( - ignoring: scroll.atBottom, - child: AnimatedOpacity( - opacity: scroll.atBottom ? 0 : 1, - duration: const Duration(milliseconds: 200), - child: FloatingActionButton.small( - onPressed: scroll.jumpToBottom, - child: const Icon(Icons.keyboard_arrow_down), - ), - ), - ), - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: MeasureSize( - onChange: (size) => composerSize.value = size.height, - child: Composer( - roomId, - node: composerNode, - onSend: - (text, {required shouldMention, required tags}) => - notifier - .send( - text, - tags: tags, - relationType: relationType.value, - shouldMention: shouldMention, - relation: relatedEvent.value, - ) - .onError(showError), - relationType: relationType.value, - relatedEvent: relatedEvent.value, - onDismiss: () => relatedEvent.value = null, - ), - ), - ), - ], - ), - ), - - if (memberListOpened == true && showMembersByDefault) - MemberList(roomId), - ], - ), - ), - ), - ); - } -} diff --git a/lib/widgets/room_menu.dart b/lib/widgets/room_menu.dart index 7488ada..95936df 100644 --- a/lib/widgets/room_menu.dart +++ b/lib/widgets/room_menu.dart @@ -1,20 +1,20 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/via.dart"; import "package:nexus/models/room.dart"; -final class const RoomMenu( - final Room? room, { - final IList children = const IList.empty(), - super.key, -}) extends ConsumerWidget { +class RoomMenu extends ConsumerWidget { + final Room? room; + final IList children; + const RoomMenu(this.room, {this.children = const IList.empty(), super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final danger = Theme.of(context).colorScheme.error; - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); return PopupMenuButton( itemBuilder: (_) => [ @@ -35,7 +35,8 @@ final class const RoomMenu( await Clipboard.setData( .new( - text: "matrix:roomid/${room!.metadata?.id.substring(1)}$vias", + text: + "matrix:roomid/${room!.metadata?.id.substring(1)}$vias)", ), ); }, diff --git a/lib/widgets/settings/dialog_list_tile.dart b/lib/widgets/settings/dialog_list_tile.dart index e66e335..f0a236c 100644 --- a/lib/widgets/settings/dialog_list_tile.dart +++ b/lib/widgets/settings/dialog_list_tile.dart @@ -1,18 +1,28 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/widgets/settings/radio_dialog.dart"; -final class const DialogListTile({ - required final T? initialValue, - required final Icon icon, - required final String title, - required final List options, - required final void Function(T value)? onChanged, - required final String Function(T option) getName, - final Widget? subtitle, - final bool required = true, - super.key, -}) extends ConsumerWidget { +class DialogListTile extends ConsumerWidget { + final T? initialValue; + final String title; + final Widget? subtitle; + final List options; + final bool required; + final Icon icon; + final void Function(T value)? onChanged; + final String Function(T option) getName; + const DialogListTile({ + super.key, + required this.icon, + required this.title, + required this.initialValue, + required this.options, + required this.onChanged, + required this.getName, + this.subtitle, + this.required = true, + }); + @override Widget build(BuildContext context, WidgetRef ref) => FormField( validator: (value) => diff --git a/lib/widgets/settings/radio_dialog.dart b/lib/widgets/settings/radio_dialog.dart index 6696139..7695b9d 100644 --- a/lib/widgets/settings/radio_dialog.dart +++ b/lib/widgets/settings/radio_dialog.dart @@ -1,14 +1,21 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -final class const RadioDialog({ - required final T? value, - required final String title, - required final List options, - required final void Function(T value)? onChanged, - required final String Function(T option) getName, - super.key, -}) extends HookWidget { +class RadioDialog extends HookWidget { + final T? value; + final String title; + final List options; + final void Function(T value)? onChanged; + final String Function(T option) getName; + const RadioDialog({ + super.key, + required this.title, + required this.value, + required this.options, + required this.onChanged, + required this.getName, + }); + @override Widget build(BuildContext context) { final mutValue = useState(null); diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index cc6d441..1bd6cf5 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -1,35 +1,33 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/spaces.dart"; import "package:nexus/models/room.dart"; -import "package:nexus/widgets/pages/notifications.dart"; -import "package:nexus/widgets/pages/settings.dart"; +import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/join_dialog.dart"; import "package:nexus/widgets/room_menu.dart"; -// Needed for navigation_rail_m3e (#65). -import "package:flutter/material.dart" as old_mat; +class Sidebar extends HookConsumerWidget { + final bool isDesktop; + const Sidebar({required this.isDesktop, super.key}); -class const Sidebar({required final bool isDesktop, super.key}) - extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedSpaceProvider = KeyController.provider( KeyController.spaceKey, ); - final selectedSpaceId = ref.watch(selectedSpaceProvider).requireValue; + final selectedSpaceId = ref.watch(selectedSpaceProvider); final selectedSpaceIdNotifier = ref.watch(selectedSpaceProvider.notifier); final selectedRoomController = KeyController.provider( KeyController.roomKey, ); - final selectedRoomId = ref.watch(selectedRoomController).requireValue; + final selectedRoomId = ref.watch(selectedRoomController); final selectedRoomIdNotifier = ref.watch(selectedRoomController.notifier); final spaces = ref.watch(SpacesController.provider); @@ -76,149 +74,126 @@ class const Sidebar({required final bool isDesktop, super.key}) shape: Border(), child: Row( children: [ - MaterialUiCompatibilityBridge( - child: Builder( - builder: (context) => old_mat.Theme( - data: old_mat.Theme.of(context).copyWith( - extensions: [ - NavigationRailM3ETheme( - itemCollapsedHeight: 48, - itemVerticalGap: 0, - ), - ], + Theme( + data: Theme.of(context).copyWith( + extensions: [ + NavigationRailM3ETheme( + itemCollapsedHeight: 48, + itemVerticalGap: 0, ), - child: Container( - color: NavigationRailTokensAdapter(context).containerColor, - padding: EdgeInsets.only(top: 16), - child: NavigationRailM3E( - type: .alwaysCollapse, - labelBehavior: .alwaysHide, - scrollable: true, - onDestinationSelected: (value) { - selectedSpaceIdNotifier.set(spaces[value].id); - selectedRoomIdNotifier.set( - spaces[value].children.firstOrNull?.metadata?.id, - ); - }, - sections: [ - .new( - destinations: spaces - .map( - (space) => NavigationRailM3EDestination( - badgeCount: switch (space.children - .addAll( - space.subSpaces - .map((element) => element.children) - .flattened, - ) - .fold( - 0, - (previousValue, room) => - previousValue + - (room.metadata?.unreadNotifications ?? - 0), - )) { - 0 => - space.children - .addAll( - space.subSpaces - .map( - (element) => - element.children, - ) - .flattened, - ) - .any( - (room) => - room - .metadata - ?.unreadMessages != - 0, - ) - ? 0 - : null, - int badgeCount => badgeCount, - }, - short: true, - icon: AvatarOrHash( - height: 28, - space.room?.metadata?.avatar, - fallback: space.icon == null - ? null - : Icon(space.icon), - space.title, - ), - label: space.title, - ), - ) - .toList(), - ), - ], - selectedIndex: selectedIndex, - trailingAtBottom: true, - trailing: Padding( - padding: .symmetric(vertical: 16), - child: Column( - spacing: 8, - children: [ - PopupMenuButton( - itemBuilder: (_) => [ - PopupMenuItem( - onTap: () => showDialog( - context: context, - builder: (_) => JoinDialog(ref), - ), - child: ListTile( - title: Text( - "Join an existing room (or space)", - ), - leading: Icon(Icons.numbers), - ), - ), - PopupMenuItem( - onTap: null, - child: ListTile( - title: Text("Create a new room"), - leading: Icon(Icons.add), - ), - ), - ], - icon: Icon(Icons.add), - ), - IconButton( - tooltip: "Explore other rooms", - onPressed: null, - icon: Icon(Icons.explore), - ), - IconButton( - tooltip: "Open notifications", - onPressed: () { - if (!isDesktop) Navigator.of(context).pop(); - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => NotificationsPage(), - ), - ); + ], + ), + child: Container( + color: NavigationRailTokensAdapter(context).containerColor, + padding: EdgeInsets.only(top: 16), + child: NavigationRailM3E( + type: .alwaysCollapse, + labelBehavior: .alwaysHide, + scrollable: true, + onDestinationSelected: (value) { + selectedSpaceIdNotifier.set(spaces[value].id); + selectedRoomIdNotifier.set( + spaces[value].children.firstOrNull?.metadata?.id, + ); + }, + sections: [ + .new( + destinations: spaces + .map( + (space) => NavigationRailM3EDestination( + badgeCount: switch (space.children + .addAll( + space.subSpaces + .map((element) => element.children) + .flattened, + ) + .fold( + 0, + (previousValue, room) => + previousValue + + (room.metadata?.unreadNotifications ?? 0), + )) { + 0 => + space.children + .addAll( + space.subSpaces + .map( + (element) => element.children, + ) + .flattened, + ) + .any( + (room) => + room.metadata?.unreadMessages != + 0, + ) + ? 0 + : null, + int badgeCount => badgeCount, }, - icon: Icon(Icons.notifications), + short: true, + icon: AvatarOrHash( + height: 28, + space.room?.metadata?.avatar, + fallback: space.icon == null + ? null + : Icon(space.icon), + space.title, + ), + label: space.title, ), - IconButton( - tooltip: "Open settings", - onPressed: () => showDialog( + ) + .toList(), + ), + ], + selectedIndex: selectedIndex, + trailingAtBottom: true, + trailing: Padding( + padding: .symmetric(vertical: 16), + child: Column( + spacing: 8, + children: [ + PopupMenuButton( + itemBuilder: (_) => [ + PopupMenuItem( + onTap: () => showDialog( context: context, - builder: (_) => SettingsPage(), + builder: (_) => JoinDialog(ref), + ), + child: ListTile( + title: Text("Join an existing room (or space)"), + leading: Icon(Icons.numbers), + ), + ), + PopupMenuItem( + onTap: null, + child: ListTile( + title: Text("Create a new room"), + leading: Icon(Icons.add), ), - icon: Icon(Icons.settings), ), ], + icon: Icon(Icons.add), ), - ), + IconButton( + tooltip: "Explore other rooms", + onPressed: null, + icon: Icon(Icons.explore), + ), + IconButton( + tooltip: "Open settings", + onPressed: () => showDialog( + context: context, + builder: (_) => SettingsPage(), + ), + icon: Icon(Icons.settings), + ), + ], ), ), ), ), ), - Expanded( child: Scaffold( backgroundColor: Colors.transparent, @@ -244,75 +219,67 @@ class const Sidebar({required final bool isDesktop, super.key}) ), ], ), - body: MaterialUiCompatibilityBridge( - child: Builder( - builder: (context) => old_mat.Theme( - data: old_mat.Theme.of(context).copyWith( - extensions: [ - NavigationRailM3ETheme( - itemExpandedHeight: 48, - iconLabelGap: 16, - ), - ], + body: Theme( + data: Theme.of(context).copyWith( + extensions: [ + NavigationRailM3ETheme( + itemExpandedHeight: 48, + iconLabelGap: 16, ), - child: NavigationRailM3E( - expandedWidth: double.infinity, - scrollable: true, - background: Colors.transparent, - type: .alwaysExpand, - selectedIndex: selectedRoomIndex ?? 0, - sections: [ - .new( - header: selectedSpace.room == null - ? null - : DividerWidget(Text("Rooms")), - destinations: roomsToDestinations( - selectedSpace.children, - ), - ), - for (final subSpace in selectedSpace.subSpaces) - .new( - header: DividerWidget( - Row( - mainAxisSize: MainAxisSize.min, - spacing: 8, - children: [ - if (subSpace.room.metadata?.avatar != null) - AvatarOrHash( - subSpace.room.metadata?.avatar, - subSpace.room.metadata?.name ?? - "Unnamed Room", - height: 16, - ), - Flexible( - child: Text( - subSpace.room.metadata?.name ?? - "Unnamed Space", - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], + ], + ), + child: NavigationRailM3E( + expandedWidth: double.infinity, + scrollable: true, + background: Colors.transparent, + type: .alwaysExpand, + selectedIndex: selectedRoomIndex ?? 0, + sections: [ + .new( + header: selectedSpace.room == null + ? null + : DividerWidget(Text("Rooms")), + destinations: roomsToDestinations(selectedSpace.children), + ), + for (final subSpace in selectedSpace.subSpaces) + .new( + header: DividerWidget( + Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + if (subSpace.room.metadata?.avatar != null) + AvatarOrHash( + subSpace.room.metadata?.avatar, + subSpace.room.metadata?.name ?? + "Unnamed Room", + height: 16, + ), + Flexible( + child: Text( + subSpace.room.metadata?.name ?? + "Unnamed Space", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), - destinations: roomsToDestinations( - subSpace.children, - ), + ], ), - ], - onDestinationSelected: (value) { - final children = selectedSpace.children.addAll( - selectedSpace.subSpaces - .map((element) => element.children) - .flattened, - ); - selectedRoomIdNotifier.set( - children[value].metadata?.id, // - ); - if (!isDesktop) Navigator.of(context).pop(); - }, - ), - ), + ), + destinations: roomsToDestinations(subSpace.children), + ), + ], + onDestinationSelected: (value) { + final children = selectedSpace.children.addAll( + selectedSpace.subSpaces + .map((element) => element.children) + .flattened, + ); + selectedRoomIdNotifier.set( + children[value].metadata?.id, // + ); + if (!isDesktop) Navigator.of(context).pop(); + }, ), ), ), diff --git a/lib/widgets/timestamp.dart b/lib/widgets/timestamp.dart deleted file mode 100644 index bcdabd1..0000000 --- a/lib/widgets/timestamp.dart +++ /dev/null @@ -1,17 +0,0 @@ -import "package:flutter/material.dart"; -import "package:timeago/timeago.dart"; - -class const Timestamp(final DateTime timestamp, {super.key}) - extends StatelessWidget { - @override - Widget build(BuildContext context) => Tooltip( - message: timestamp.toString(), - child: Text( - format(timestamp), - maxLines: 1, - overflow: .ellipsis, - style: Theme.of(context).textTheme.labelSmall - ?.copyWith(color: Colors.grey), - ), - ); -} diff --git a/lib/widgets/url_preview.dart b/lib/widgets/url_preview.dart index 5eda427..3a20e19 100644 --- a/lib/widgets/url_preview.dart +++ b/lib/widgets/url_preview.dart @@ -1,11 +1,14 @@ -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/url_preview.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/mxc_image.dart"; -class const UrlPreview(final Uri link, {super.key}) extends ConsumerWidget { +class UrlPreview extends ConsumerWidget { + final Uri link; + const UrlPreview(this.link, {super.key}); + @override Widget build(BuildContext context, WidgetRef ref) => ConstrainedBox( constraints: .loose(.fromWidth(400)), @@ -18,9 +21,9 @@ class const UrlPreview(final Uri link, {super.key}) extends ConsumerWidget { onTap: () => ref.watch(LaunchHelper.provider).launchUrl(link), child: Card( margin: .symmetric(vertical: 4), - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, child: Padding( padding: .all(16), child: Column( @@ -31,15 +34,9 @@ class const UrlPreview(final Uri link, {super.key}) extends ConsumerWidget { Text( preview.title!, style: Theme.of(context).textTheme.titleLarge, - maxLines: 3, - overflow: .ellipsis, ), if (preview.description != null) ...[ - Text( - preview.description!, - maxLines: 20, - overflow: .ellipsis, - ), + Text(preview.description!), SizedBox(height: 4), ], if (preview.imageUrl != null) diff --git a/lib/widgets/user_bottom_sheet.dart b/lib/widgets/user_bottom_sheet.dart index 9fbdf35..15a24a0 100644 --- a/lib/widgets/user_bottom_sheet.dart +++ b/lib/widgets/user_bottom_sheet.dart @@ -1,5 +1,5 @@ import "package:collection/collection.dart"; -import "package:material_ui/material_ui.dart"; +import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; @@ -16,17 +16,17 @@ import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/main.dart"; import "package:nexus/widgets/expandable_image.dart"; -final class const UserBottomSheet( - final MembershipContent member, - final String userId, { - final String? roomId, - super.key, -}) extends ConsumerWidget { +class UserBottomSheet extends ConsumerWidget { + final MembershipContent member; + final String userId; + final String? roomId; + const UserBottomSheet(this.member, this.userId, {this.roomId, super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final textTheme = theme.textTheme; - final client = ref.read(ClientController.provider.notifier); + final client = ref.watch(ClientController.provider.notifier); void showMembershipDialog(MembershipAction action) => showDialog( context: context, @@ -200,35 +200,36 @@ final class const UserBottomSheet( ), if (ref.watch( - PowerLevelController.provider( - .membershipAction( - action: .kick, - roomId: roomId!, - targetUser: userId, - ), - ), - )) + PowerLevelController.provider( + .membershipAction( + action: .kick, + roomId: roomId!, + targetUser: userId, + ), + ), + ) && + member.status == .join || + member.status == .invite) Padding( padding: .only(top: 4), child: Row( mainAxisSize: MainAxisSize.max, spacing: 8, children: [ - if (member.status == .join || member.status == .invite) - M3EButton.icon( - onPressed: () => showMembershipDialog(.kick), - shape: .square, - icon: Icon(Icons.sports_martial_arts), - label: Text("Kick"), - decoration: .new( - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.error, - ), - foregroundColor: WidgetStatePropertyAll( - theme.colorScheme.onError, - ), + M3EButton.icon( + onPressed: () => showMembershipDialog(.kick), + shape: .square, + icon: Icon(Icons.sports_martial_arts), + label: Text("Kick"), + decoration: .new( + backgroundColor: WidgetStatePropertyAll( + theme.colorScheme.error, + ), + foregroundColor: WidgetStatePropertyAll( + theme.colorScheme.onError, ), ), + ), M3EButton.icon( onPressed: () => showMembershipDialog(.ban), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 70d9ba2..603ea6a 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -38,9 +37,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); - g_autoptr(FlPluginRegistrar) webcrypto_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "WebcryptoPlugin"); - webcrypto_plugin_register_with_registrar(webcrypto_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1d53ad7..7d8f046 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -10,7 +10,6 @@ list(APPEND FLUTTER_PLUGIN_LIST media_kit_video screen_retriever_linux url_launcher_linux - webcrypto window_manager ) diff --git a/linux/nexus.federated.nexus.UnifiedPush.service b/linux/nexus.federated.nexus.UnifiedPush.service deleted file mode 100644 index 663ab9b..0000000 --- a/linux/nexus.federated.nexus.UnifiedPush.service +++ /dev/null @@ -1,3 +0,0 @@ -[D-BUS Service] -Name=nexus.federated.nexus.UnifiedPush -Exec=/usr/bin/env FLUTTER_HEADLESS=1 nexus diff --git a/linux/nexus.federated.nexus.desktop b/linux/nexus.federated.nexus.desktop index 64b4264..d61734e 100644 --- a/linux/nexus.federated.nexus.desktop +++ b/linux/nexus.federated.nexus.desktop @@ -2,9 +2,9 @@ Name=Nexus GenericName=Matrix Client Comment=A simple and user-friendly Matrix client -Exec=nexus +Exec=nexus %u Icon=nexus Terminal=false Type=Application Categories=Chat;Network;InstantMessaging; -DBusActivatable=true \ No newline at end of file +MimeType=x-scheme-handler/nexus.federated.nexus; \ No newline at end of file diff --git a/linux/nexus.federated.nexus.service b/linux/nexus.federated.nexus.service deleted file mode 100644 index 1757c2c..0000000 --- a/linux/nexus.federated.nexus.service +++ /dev/null @@ -1,3 +0,0 @@ -[D-BUS Service] -Name=nexus.federated.nexus -Exec=/usr/bin/env nexus diff --git a/linux/nexus.federated.nexus.uri.desktop b/linux/nexus.federated.nexus.uri.desktop deleted file mode 100644 index 10f495f..0000000 --- a/linux/nexus.federated.nexus.uri.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Name=Nexus URI Handler -Exec=nexus %u -Icon=nexus -Terminal=false -Type=Application -NoDisplay=true -MimeType=x-scheme-handler/nexus.federated.nexus; \ No newline at end of file diff --git a/linux/nix/default.nix b/linux/nix/default.nix deleted file mode 100644 index 756eb0b..0000000 --- a/linux/nix/default.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ self, inputs, ... }: { - - perSystem = - { - pkgs, - system, - lib, - ... - }: - { - _module.args.pkgs = import inputs.nixpkgs { - inherit system; - config = { - android_sdk.accept_license = true; - allowUnfree = true; - }; - }; - - packages = - let - default = pkgs.callPackage ./pkg { - src = self; - }; - in - { - inherit default; - - flatpak = inputs.nix2flatpak.lib.${system}.mkFlatpak { - appName = "Nexus"; - developer = "QuadRadical"; - appId = "nexus.federated.nexus"; - package = default; - runtime = "org.gnome.Platform/49"; - permissions = { - share = [ "network" ]; - sockets = [ - "pulseaudio" - "fallback-x11" - "wayland" - ]; - - talk-names = [ - "org.unifiedpush.Distributor.*" - "org.freedesktop.Notifications" - ]; - devices = [ "dri" ]; - }; - }; - - gomuks = pkgs.callPackage ./pkg/gomuks.nix { - src = self; - }; - }; - - devShells.default = pkgs.callPackage ./devshell.nix { }; - }; - - flake.nixosModules.default = import ./module.nix self; -} diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix index 8bc8641..812082c 100644 --- a/linux/nix/devshell.nix +++ b/linux/nix/devshell.nix @@ -2,7 +2,7 @@ let android = pkgs.androidenv.composeAndroidPackages { toolsVersion = "26.1.1"; - platformToolsVersion = "37.0.1"; + platformToolsVersion = "36.0.1"; buildToolsVersions = [ "35.0.0" "36.0.0" @@ -23,7 +23,7 @@ pkgs.mkShell { [ go git - + jdk17 libGL (flutter.override { extraPkgConfigPackages = [ @@ -40,11 +40,13 @@ pkgs.mkShell { ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; ANDROID_SDK_ROOT = ANDROID_HOME; - JAVA_HOME = pkgs.jdk21; + JAVA_HOME = pkgs.jdk17; - GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; + TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}"; + GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2"; } // lib.optionalAttrs pkgs.stdenv.isLinux { + CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ]; LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; }; } diff --git a/linux/nix/module.nix b/linux/nix/module.nix deleted file mode 100644 index a8abd6c..0000000 --- a/linux/nix/module.nix +++ /dev/null @@ -1,45 +0,0 @@ -self: -{ - pkgs, - lib, - config, - ... -}: - -let - inherit (lib) - mkIf - mkMerge - mkEnableOption - mkPackageOption - ; - - cfg = config.programs.nexus; -in -{ - options.programs.nexus = { - enable = mkEnableOption "Nexus, a simple and user-friendly Matrix client"; - package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "default" { }; - - enableNotifications = mkEnableOption "notifications support via UnifiedPush"; - }; - - config = mkIf cfg.enable (mkMerge [ - { - environment.systemPackages = [ cfg.package ]; - } - (mkIf cfg.enableNotifications { - environment.systemPackages = [ - pkgs.kdePackages.kunifiedpush - ]; - - systemd.packages = [ - pkgs.kdePackages.kunifiedpush - ]; - - services.dbus.packages = [ - cfg.package - ]; - }) - ]); -} diff --git a/linux/nix/pkg/default.nix b/linux/nix/pkg/default.nix index 3e55dfe..c026606 100644 --- a/linux/nix/pkg/default.nix +++ b/linux/nix/pkg/default.nix @@ -32,15 +32,11 @@ flutter.buildFlutterApplication { emoji_text_field = "sha256-3TOys09EP2GRo6pUBGPXaqBlE39O2Cmwt42Hs1cTDKo="; linkify = "sha256-TpMD6+0zyY6i9l+6d8ErnVufmepCv362rCtnbOht/z4="; navigation_rail_m3e = "sha256-+2awDTQnK58gGRY1nuHckG/jjxarsYSRu9ovR4i4TEc="; - unifiedpush_linux = "sha256-aIF/8oabSuuZo7K6qr4r7UBmf57qgoGAXyJ40fdntuQ="; - unifiedpush_platform_interface = "sha256-aIF/8oabSuuZo7K6qr4r7UBmf57qgoGAXyJ40fdntuQ="; - xdg_desktop_portal = "sha256-As1X1/IfCbSD5ful2kAxgBita95KxmInRN4/6QmVaTI="; }; postInstall = '' - install -D assets/bundled/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg - install -Dm755 linux/*.desktop -t $out/share/applications - install -Dm644 linux/*.service -t $out/share/dbus-1/services + install -D assets/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg + install -Dm755 linux/nexus.federated.nexus.desktop -t $out/share/applications wrapProgram $out/bin/nexus \ --suffix LD_LIBRARY_PATH : $out/app/nexus/lib ''; diff --git a/linux/nix/pkg/gomuks.nix b/linux/nix/pkg/gomuks.nix index 64e0faa..ca94198 100644 --- a/linux/nix/pkg/gomuks.nix +++ b/linux/nix/pkg/gomuks.nix @@ -17,7 +17,7 @@ buildGoModule ( src = "${src}/gomuks"; - vendorHash = "sha256-rLLDbNrYf5HqG3Y6zihRi1v+GqGeNAtZUL+8QHdCz7w="; + vendorHash = "sha256-fnRuw+zt6Iofv0kqypx+jy1gLvOHTQvJsOwSKu7kGLs="; buildPhase = '' runHook preBuild diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index bf6e5ef..62e5450 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -1,11 +1,6 @@ #include "my_application.h" -#include - #include -#include -#include - #ifdef GDK_WINDOWING_X11 #include #endif @@ -14,384 +9,144 @@ struct _MyApplication { GtkApplication parent_instance; - char** dart_entrypoint_arguments; - - FlEngine* flutter_engine; - FlMethodChannel* notification_channel; - - gchar* pending_notification_event_id; - gboolean flutter_ready; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) -static void my_application_activate(GApplication* application); - -static void send_notification_event_to_flutter( - MyApplication* self, - const gchar* event_id) { - if (self->notification_channel == nullptr || - !self->flutter_ready) { - g_free(self->pending_notification_event_id); - self->pending_notification_event_id = g_strdup(event_id); - return; - } - - g_autoptr(FlValue) args = - fl_value_new_string(event_id); - - fl_method_channel_invoke_method( - self->notification_channel, - "notificationClicked", - args, - nullptr, - nullptr, - nullptr); +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView *view) +{ + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); } -static void notification_event_action( - GSimpleAction* action, - GVariant* parameter, - gpointer user_data) { - MyApplication* self = - MY_APPLICATION(user_data); +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); - if (parameter == nullptr) { - g_warning( - "Notification event action invoked without a target"); - return; - } - - if (!g_variant_is_of_type( - parameter, - G_VARIANT_TYPE_STRING)) { - g_warning( - "Notification event action received unexpected parameter type: %s", - g_variant_get_type_string(parameter)); - return; - } - - const gchar* event_id = - g_variant_get_string(parameter, nullptr); - - g_message( - "Notification event activated: %s", - event_id); - - send_notification_event_to_flutter( - self, - event_id); - - GList* windows = - gtk_application_get_windows( - GTK_APPLICATION(self)); - - if (windows != nullptr) { - gtk_window_present( - GTK_WINDOW(windows->data)); - } else { - my_application_activate( - G_APPLICATION(self)); - } -} - -static void first_frame_cb( - MyApplication* self, - FlView* view) { - self->flutter_ready = TRUE; - - gtk_widget_show( - gtk_widget_get_toplevel( - GTK_WIDGET(view))); - - if (self->pending_notification_event_id != nullptr) { - gchar* event_id = - self->pending_notification_event_id; - - self->pending_notification_event_id = nullptr; - - send_notification_event_to_flutter( - self, - event_id); - - g_free(event_id); - } -} - -static void my_application_activate( - GApplication* application) { - MyApplication* self = - MY_APPLICATION(application); - - GList* windows = - gtk_application_get_windows( - GTK_APPLICATION(application)); - - if (windows != nullptr) { - gtk_window_present( - GTK_WINDOW(windows->data)); + GList* windows = gtk_application_get_windows(GTK_APPLICATION(application)); + if (windows) { + gtk_window_present(GTK_WINDOW(windows->data)); return; } GtkWindow* window = - GTK_WINDOW( - gtk_application_window_new( - GTK_APPLICATION(application))); + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). gboolean use_header_bar = TRUE; - #ifdef GDK_WINDOWING_X11 - GdkScreen* screen = - gtk_window_get_screen(window); - + GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = - gdk_x11_screen_get_window_manager_name(screen); - + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif - - gtk_widget_set_size_request( - GTK_WIDGET(window), - 250, - -1); - + gtk_widget_set_size_request(GTK_WIDGET(window), 250, -1); if (use_header_bar) { - GtkHeaderBar* header_bar = - GTK_HEADER_BAR( - gtk_header_bar_new()); - - gtk_widget_show( - GTK_WIDGET(header_bar)); - - gtk_header_bar_set_title( - header_bar, - "nexus"); - - gtk_header_bar_set_show_close_button( - header_bar, - TRUE); - - gtk_window_set_titlebar( - window, - GTK_WIDGET(header_bar)); + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "nexus"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title( - window, - "nexus"); + gtk_window_set_title(window, "nexus"); } - gtk_window_set_default_size( - window, - 1280, - 720); + gtk_window_set_default_size(window, 1280, 720); - g_autoptr(FlDartProject) project = - fl_dart_project_new(); - - fl_dart_project_set_dart_entrypoint_arguments( - project, - self->dart_entrypoint_arguments); - - FlView* view = - fl_view_new(project); - - self->flutter_engine = - fl_view_get_engine(view); - - g_autoptr(FlStandardMethodCodec) codec = - fl_standard_method_codec_new(); - - self->notification_channel = - fl_method_channel_new( - fl_engine_get_binary_messenger( - self->flutter_engine), - "nexus/notifications", - FL_METHOD_CODEC(codec)); + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + FlView* view = fl_view_new(project); GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - gdk_rgba_parse( - &background_color, - "#000000"); + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); + gtk_widget_realize(GTK_WIDGET(view)); - fl_view_set_background_color( - view, - &background_color); + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - gtk_widget_show( - GTK_WIDGET(view)); - - if (std::getenv("FLUTTER_HEADLESS")) { - gtk_widget_hide( - GTK_WIDGET(window)); - } - - gtk_container_add( - GTK_CONTAINER(window), - GTK_WIDGET(view)); - - g_signal_connect_swapped( - view, - "first-frame", - G_CALLBACK(first_frame_cb), - self); - - gtk_widget_realize( - GTK_WIDGET(view)); - - fl_register_plugins( - FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus( - GTK_WIDGET(view)); + gtk_widget_grab_focus(GTK_WIDGET(view)); } -static gboolean my_application_local_command_line( - GApplication* application, - gchar*** arguments, - int* exit_status) { - MyApplication* self = - MY_APPLICATION(application); - - self->dart_entrypoint_arguments = - g_strdupv(*arguments + 1); +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; - - if (!g_application_register( - application, - nullptr, - &error)) { - g_warning( - "Failed to register: %s", - error->message); - - *exit_status = 1; - return TRUE; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); - *exit_status = 0; return FALSE; } -static void my_application_startup( - GApplication* application) { - g_message( - "GApplication application-id: %s", - g_application_get_application_id( - application)); +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); - g_message( - "GApplication is remote: %d", - g_application_get_is_remote( - application)); + // Perform any actions required at application startup. - g_message( - "GApplication is registered: %d", - g_application_get_is_registered( - application)); - - g_message( - "prgname: %s", - g_get_prgname()); - - G_APPLICATION_CLASS( - my_application_parent_class) - ->startup(application); + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); } -static void my_application_shutdown( - GApplication* application) { - G_APPLICATION_CLASS( - my_application_parent_class) - ->shutdown(application); +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); } -static void my_application_dispose( - GObject* object) { - MyApplication* self = - MY_APPLICATION(object); - - g_clear_pointer( - &self->dart_entrypoint_arguments, - g_strfreev); - - g_clear_pointer( - &self->pending_notification_event_id, - g_free); - - g_clear_object( - &self->notification_channel); - - self->flutter_engine = nullptr; - - G_OBJECT_CLASS( - my_application_parent_class) - ->dispose(object); +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } -static void my_application_class_init( - MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = - my_application_activate; - - G_APPLICATION_CLASS(klass)->local_command_line = - my_application_local_command_line; - - G_APPLICATION_CLASS(klass)->startup = - my_application_startup; - - G_APPLICATION_CLASS(klass)->shutdown = - my_application_shutdown; - - G_OBJECT_CLASS(klass)->dispose = - my_application_dispose; +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } -static void my_application_init( - MyApplication* self) { - self->dart_entrypoint_arguments = nullptr; - self->flutter_engine = nullptr; - self->notification_channel = nullptr; - self->pending_notification_event_id = nullptr; - self->flutter_ready = FALSE; - - GSimpleAction* action = - g_simple_action_new( - "event", - G_VARIANT_TYPE_STRING); - - g_signal_connect( - action, - "activate", - G_CALLBACK(notification_event_action), - self); - - g_action_map_add_action( - G_ACTION_MAP(self), - G_ACTION(action)); - - g_object_unref(action); -} +static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. g_set_prgname(APPLICATION_ID); - return MY_APPLICATION( - g_object_new( - my_application_get_type(), - "application-id", - APPLICATION_ID, - "flags", - G_APPLICATION_HANDLES_COMMAND_LINE | - G_APPLICATION_HANDLES_OPEN, - nullptr)); -} \ No newline at end of file + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, + nullptr)); +} diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index 4b81f9b..c2efd0b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index 5caa9d1..c2efd0b 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8b6bbb6..f18d831 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import app_links import dynamic_color import file_selector_macos -import flutter_local_notifications import media_kit_libs_macos_video import media_kit_video import package_info_plus @@ -16,14 +15,12 @@ import screen_retriever_macos import shared_preferences_foundation import url_launcher_macos import wakelock_plus -import webcrypto import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) - FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) @@ -31,6 +28,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) - WebcryptoPlugin.register(with: registry.registrar(forPlugin: "WebcryptoPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/macos/Podfile b/macos/Podfile deleted file mode 100644 index 65543e2..0000000 --- a/macos/Podfile +++ /dev/null @@ -1,42 +0,0 @@ -platform :osx, '13.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/macos/Podfile.lock b/macos/Podfile.lock deleted file mode 100644 index 08cb773..0000000 --- a/macos/Podfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - - media_kit_libs_macos_video (1.0.4): - - FlutterMacOS - - media_kit_video (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) - - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - media_kit_libs_macos_video: - :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos - media_kit_video: - :path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos - -SPEC CHECKSUMS: - FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 - media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758 - -PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 - -COCOAPODS: 1.17.0 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index fa18d44..f87523c 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,9 +27,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 34AEA7295333B7F48CD3BE4C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D76F30AE9CCFCA04C354BD4C /* Pods_RunnerTests.framework */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - BAF79690465DBAC1A1E19D28 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70FD05041ACDA7E801BB9823 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,12 +60,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 18EDDEF1FB58EC56ABDA1BEB /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* nexus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = nexus.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* nexus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "nexus.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -80,16 +76,8 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 5099E6191AE87E21D356D9C8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 6D9F8F8BBED102075ECF1DF9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 70FD05041ACDA7E801BB9823 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 8F5ECEC7BF9DD35CB3E7196E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 9DDE815F827B87E2C55DF94C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - CBD1D9203BEE9731F981EEF8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - D76F30AE9CCFCA04C354BD4C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,7 +85,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 34AEA7295333B7F48CD3BE4C /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,8 +92,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - BAF79690465DBAC1A1E19D28 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -140,7 +125,6 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - FC780AF47288088B9004ACE2 /* Pods */, ); sourceTree = ""; }; @@ -167,7 +151,6 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -192,26 +175,10 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - 70FD05041ACDA7E801BB9823 /* Pods_Runner.framework */, - D76F30AE9CCFCA04C354BD4C /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; - FC780AF47288088B9004ACE2 /* Pods */ = { - isa = PBXGroup; - children = ( - 6D9F8F8BBED102075ECF1DF9 /* Pods-Runner.debug.xcconfig */, - 9DDE815F827B87E2C55DF94C /* Pods-Runner.release.xcconfig */, - CBD1D9203BEE9731F981EEF8 /* Pods-Runner.profile.xcconfig */, - 18EDDEF1FB58EC56ABDA1BEB /* Pods-RunnerTests.debug.xcconfig */, - 8F5ECEC7BF9DD35CB3E7196E /* Pods-RunnerTests.release.xcconfig */, - 5099E6191AE87E21D356D9C8 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -219,7 +186,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - C413A8A75594D2165C9D56BD /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -238,13 +204,11 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 446C82D5FE82BD05F78EDA6A /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - C4D158541D7059C07D3CA9B3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -252,9 +216,6 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* nexus.app */; productType = "com.apple.product-type.application"; @@ -299,9 +260,6 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -371,67 +329,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 446C82D5FE82BD05F78EDA6A /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C413A8A75594D2165C9D56BD /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C4D158541D7059C07D3CA9B3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -483,7 +380,6 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 18EDDEF1FB58EC56ABDA1BEB /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -498,7 +394,6 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F5ECEC7BF9DD35CB3E7196E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -513,7 +408,6 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5099E6191AE87E21D356D9C8 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -567,7 +461,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -649,7 +543,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -699,7 +593,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -806,20 +700,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 746b383..ff47eeb 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,24 +5,6 @@ - - - - - - - - - - - - diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 96d3fee..a2ec33f 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "info": { - "version": 1, - "author": "xcode" + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" }, - "images": [ - { - "size": "16x16", - "idiom": "mac", - "filename": "app_icon_16.png", - "scale": "1x" - }, - { - "size": "16x16", - "idiom": "mac", - "filename": "app_icon_32.png", - "scale": "2x" - }, - { - "size": "32x32", - "idiom": "mac", - "filename": "app_icon_32.png", - "scale": "1x" - }, - { - "size": "32x32", - "idiom": "mac", - "filename": "app_icon_64.png", - "scale": "2x" - }, - { - "size": "128x128", - "idiom": "mac", - "filename": "app_icon_128.png", - "scale": "1x" - }, - { - "size": "128x128", - "idiom": "mac", - "filename": "app_icon_256.png", - "scale": "2x" - }, - { - "size": "256x256", - "idiom": "mac", - "filename": "app_icon_256.png", - "scale": "1x" - }, - { - "size": "256x256", - "idiom": "mac", - "filename": "app_icon_512.png", - "scale": "2x" - }, - { - "size": "512x512", - "idiom": "mac", - "filename": "app_icon_512.png", - "scale": "1x" - }, - { - "size": "512x512", - "idiom": "mac", - "filename": "app_icon_1024.png", - "scale": "2x" - } - ] -} \ No newline at end of file + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 1682af0..82b6f9d 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index c2e488b..13b35eb 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index e2bf256..0a3f5fa 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index 983d7fa..bdb5722 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index c8f0676..f083318 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 468be1f..326c0e7 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index a77207d..2f1632c 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/pubspec.lock b/pubspec.lock index 51e0bd6..c989ed4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: fdcd9f70f9eb80df3bc5ed0fa67280df2595fd481948df8a9ab082e6a40ad04b + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c url: "https://pub.dev" source: hosted - version: "108.0.0" + version: "99.0.0" analysis_server_plugin: dependency: transitive description: name: analysis_server_plugin - sha256: "3460f040e1f77647fcd82db9bd3bb117de04d04a3d67c39ad361794331147a4e" + sha256: "3960b28ee740004df39f85d5ebfc91785f7a90e51fd7c9a185e86a36b2f581b4" url: "https://pub.dev" source: hosted - version: "0.3.23" + version: "0.3.14" analyzer: dependency: transitive description: name: analyzer - sha256: a51c769bff3b6dfbe9d60199b8606d808290702a296bef0c26a4ca391d414e46 + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" url: "https://pub.dev" source: hosted - version: "14.4.0" + version: "12.1.0" analyzer_buffer: dependency: transitive description: name: analyzer_buffer - sha256: f890a2e708e38fbf4238ad2f9fd3d506538acdddb0cd8eb2c3e8c7bd17784b57 + sha256: "445b77e2054fa3e8c8a8ef1b5e9e6b23bb8028fffd34b5e60eaef315b7750674" url: "https://pub.dev" source: hosted - version: "0.3.4" + version: "0.3.3" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "9a3518eb84e8e26665874d9867534961d855493e9c5eb0eb55cbd46edd995529" + sha256: "0057a98d64d7bb872b0c87dff6e73d2c2d80c77156e7a03f127a26f8aa240649" url: "https://pub.dev" source: hosted - version: "0.14.17" + version: "0.14.8" app_links: dependency: "direct main" description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: archive - sha256: "6c5bcd986e06b94e3c40244af471750840a3d2341d1f9763a1100a14add517b4" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.0.9" args: dependency: transitive description: @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" - asn1lib: - dependency: transitive - description: - name: asn1lib - sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" - url: "https://pub.dev" - source: hosted - version: "1.6.5" async: dependency: transitive description: @@ -117,34 +109,34 @@ packages: dependency: transitive description: name: build - sha256: "8a5c5761af8e31748bba3c82f68925ace40f3225c3eea25be9beb57eca7cd7a8" + sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" url: "https://pub.dev" source: hosted - version: "4.0.11" + version: "4.0.7" build_config: dependency: transitive description: name: build_config - sha256: d466ed2dc9c6cd1d169948879b84ee061eb5e22c64a7c6089879c6296d272a8d + sha256: f2c223156a26eea323e6244b85141d76413a80aeee9fe0b380773789fabaf8ae url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: e1d40ef3f7934986d5da2271b1ba07794921ce263e44d622fb6c406d76589e33 + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 url: "https://pub.dev" source: hosted - version: "4.1.6" + version: "4.1.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "894c243f6bc32015fec466ce30a6925bd537a77a426ee5bf481120477eb3de67" + sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" url: "https://pub.dev" source: hosted - version: "2.16.1" + version: "2.15.1" built_collection: dependency: transitive description: @@ -157,10 +149,10 @@ packages: dependency: transitive description: name: built_value - sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7 + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.13.0" + version: "8.12.6" button_m3e: dependency: transitive description: @@ -177,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -197,18 +205,18 @@ packages: dependency: transitive description: name: clock - sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.2" code_assets: dependency: "direct main" description: name: code_assets - sha256: "828110d598123b5ea96c00c9f3c72105bf79f8ee36c20a39b26209ade421ec57" + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "1.2.1" collection: dependency: "direct main" description: @@ -233,14 +241,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" cross_file: dependency: transitive description: name: cross_file - sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6 + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+5" + version: "0.3.5+4" crypto: dependency: transitive description: @@ -257,54 +273,47 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - cupertino_ui: - dependency: transitive - description: - name: cupertino_ui - sha256: c23816577a1280cff409e989541767887011f8f1d8c1863a794ed308af46f2a9 - url: "https://pub.dev" - source: hosted - version: "1.1.1" dart_style: dependency: transitive description: name: dart_style - sha256: "82ade9fc4273f29ed673e33166944465225b4f7fc5d4aaef48605cc751c18fc1" + sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 url: "https://pub.dev" source: hosted - version: "3.1.13" + version: "3.1.8" dbus: dependency: transitive description: name: dbus - sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" url: "https://pub.dev" source: hosted - version: "0.7.15" + version: "0.7.14" dynamic_color: dependency: "direct main" description: name: dynamic_color - sha256: "869b3bce0100eb519768ecd71c0111f0b5ea679f5f62e1f59c49d80067e730c5" + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "1.8.1" dynamic_polls: dependency: "direct main" description: name: dynamic_polls - sha256: "58cd1fafbbaba6b485a61692aa3bd33ded6f0f61734543b6c754511c362d8ae1" + sha256: "72ff19cdf041ad8dcfa76adaebb216d005f40b278d955e6e0c7bcb769215fabe" url: "https://pub.dev" source: hosted - version: "0.0.8" - encrypt: - dependency: transitive + version: "0.0.7" + emoji_text_field: + dependency: "direct main" description: - name: encrypt - sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" - url: "https://pub.dev" - source: hosted - version: "5.0.3" + path: "." + ref: HEAD + resolved-ref: "5f7baaf8a6f059ec3ab8ff0f5d02339b00bf6997" + url: "https://github.com/Henry-Hiles/emoji_text_field" + source: git + version: "1.0.0" equatable: dependency: transitive description: @@ -357,10 +366,10 @@ packages: dependency: "direct main" description: name: ffigen - sha256: "31b2ca630cede89babbbf31688d20b735967c51bf60572b06ec16718e5a7f1ec" + sha256: b7803707faeec4ce3c1b0c2274906504b796e3b70ad573577e72333bd1c9b3ba url: "https://pub.dev" source: hosted - version: "22.0.0" + version: "20.1.1" file: dependency: transitive description: @@ -381,34 +390,34 @@ packages: dependency: transitive description: name: file_selector_android - sha256: d670cd0ce77a2e785b18d8b4d0a8d6a222d6a813ec9b7ddf2790a1b4fb6fa92c + sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b" url: "https://pub.dev" source: hosted - version: "0.5.2+11" + version: "0.5.2+8" file_selector_ios: dependency: transitive description: name: file_selector_ios - sha256: "97269e5307a0ab813b1fa2430bada0a96e0afb74848417f8676f64ba5de0051c" + sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca url: "https://pub.dev" source: hosted - version: "0.5.3+6" + version: "0.5.3+5" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: da76400e7872ce7637ffdce12749ec24169c25f6195c28372208e65a24bcd2ab + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" url: "https://pub.dev" source: hosted - version: "0.9.4+1" + version: "0.9.4" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: d57c62362766b5e7ae739448650b66c6aab7a68ba7ecc65e04018652645ae0f4 + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" url: "https://pub.dev" source: hosted - version: "0.9.5+1" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: @@ -429,10 +438,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: fbefc5fb92c6d3cbe8d284a2cd971b593bb07d2cd6da8557b81a862250b4acec + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" url: "https://pub.dev" source: hosted - version: "0.9.3+6" + version: "0.9.3+5" fixnum: dependency: transitive description: @@ -486,46 +495,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "357be7ff996dc272e2037c69c1bdd5d1bd21172219d55f8bd2e11aeea602e6d3" - url: "https://pub.dev" - source: hosted - version: "22.3.1" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b" - url: "https://pub.dev" - source: hosted - version: "8.0.1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "43c3761d916c9bd3d5c7ebbc44d82f4990329840c0c5d62ad5260cc1b5d399bd" - url: "https://pub.dev" - source: hosted - version: "12.2.0" - flutter_local_notifications_web: - dependency: transitive - description: - name: flutter_local_notifications_web - sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_local_notifications_windows: - dependency: transitive - description: - name: flutter_local_notifications_windows - sha256: "6f43bdd03b171b7a90f22647506fea33e2bb12294b7c7c7a3d690e960a382945" - url: "https://pub.dev" - source: hosted - version: "3.1.1" flutter_localizations: dependency: "direct main" description: flutter @@ -543,10 +512,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "2b7f9d2a3c730ac1a98221b420cef966b990fca7ab546ede324642ca57a533e3" + sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e" url: "https://pub.dev" source: hosted - version: "3.4.3" + version: "3.3.2" flutter_svg: dependency: "direct main" description: @@ -569,10 +538,10 @@ packages: dependency: "direct main" description: name: flutter_widget_from_html_core - sha256: "530f2e3cc57be1a00f8bfae1b0000064e8a6df0cf1fb4d48a80f85afde9b39b7" + sha256: "7ff010b116f6abc16429923e616fbc727f3f65ef4cee12ffdb280aeecbc21e7f" url: "https://pub.dev" source: hosted - version: "0.17.4" + version: "0.17.2" fluttertagger: dependency: "direct main" description: @@ -585,10 +554,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "3af98c92d7d4dd182ea1393193e69a840fcbaee1f66f1158bd20504e5f3f6f4f" + sha256: "8599ba37236328ff6f97269ecce875d251a367eb2929c9bbf9b811b2ce56c74e" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "3.2.6-dev.1" freezed_annotation: dependency: "direct main" description: @@ -597,22 +566,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" get_x_storage: dependency: transitive description: name: get_x_storage - sha256: "597cd8f6ef8b01c747a6d95ff7b0310066f6c82ddf5565ca371f047f316fe43e" + sha256: "69e4412dd70e25a4991623c10bf72e3b12106f2cb4353a2d167353947597f3aa" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.0.9" glob: dependency: transitive description: name: glob - sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.3" graphs: dependency: transitive description: @@ -633,26 +610,26 @@ packages: dependency: "direct main" description: name: hooks - sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.0.2" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - sha256: d58e1e14aa112ca9c56d54effb7d0d717b2d3f96dad3cdd1fce16f8740450700 + sha256: dcaf59f0f41489ff08ce61438ada564d67f40cfa37cc7c8589da78fb600c2edc url: "https://pub.dev" source: hosted - version: "3.4.3" + version: "3.3.2" html: dependency: transitive description: name: html - sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.7" + version: "0.15.6" http: dependency: "direct main" description: @@ -689,10 +666,10 @@ packages: dependency: transitive description: name: image - sha256: a1e7f4951e538a568e14b856702afc9ae1d2f4b202daced8d22c1b9cd211ce89 + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.8.0" image_picker: dependency: "direct main" description: @@ -705,10 +682,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "0a55d645a670d6ae11efa948f955ae904afa472f1344fc15a7ddacc20c1e7219" + sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" url: "https://pub.dev" source: hosted - version: "0.8.13+23" + version: "0.8.13+19" image_picker_for_web: dependency: transitive description: @@ -721,10 +698,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "852a7762962f3bb3ad54ef1d342669e41557f466033a676baa8876b367cf4050" + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 url: "https://pub.dev" source: hosted - version: "0.8.13+8" + version: "0.8.13+6" image_picker_linux: dependency: transitive description: @@ -761,26 +738,18 @@ packages: dependency: "direct main" description: name: intl - sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.20.3" + version: "0.20.2" io: dependency: transitive description: name: io - sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.1.0" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" + version: "1.0.5" json_annotation: dependency: "direct main" description: @@ -793,10 +762,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: e45aefa0324f08c683caafbb94b72837aa6193c61822799c916e45f4a263113d + sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 url: "https://pub.dev" source: hosted - version: "6.14.1" + version: "6.14.0" leak_tracker: dependency: transitive description: @@ -838,14 +807,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" - listen: - dependency: transitive - description: - name: listen - sha256: "47501a08016a43fcad79252439d723f50f14f88fa7bfd8a177e0a417e5c9e1f2" - url: "https://pub.dev" - source: hosted - version: "1.0.1" logging: dependency: transitive description: @@ -858,18 +819,18 @@ packages: dependency: "direct main" description: name: m3e_buttons - sha256: dca780e813563754576194ed18e3f6a33422ed136c8d4ccfeca20909edddd728 + sha256: af1b19bd5ec7327d20b5a57c6c91e1ed30ee15a4dbade781c0cdbc72e6b04b5e url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "0.0.4" m3e_card_list: dependency: "direct main" description: name: m3e_card_list - sha256: ed1cc150f051b8812ffd735915ae59fed23517ffeeed04c4b2b8ef6257cc18b7 + sha256: d4aba0123cccda40ac80789befa8d355e1dc16aa7dcee910157690b0546d78d6 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "0.1.0" m3e_design: dependency: transitive description: @@ -882,10 +843,10 @@ packages: dependency: transitive description: name: matcher - sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.20" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -894,22 +855,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" - material_emoji_picker: - dependency: "direct main" - description: - name: material_emoji_picker - sha256: fff8b05f9f8c4a0da552397b03823b4fe351e98ec8aa4ed46e3ab9a7f6655c83 - url: "https://pub.dev" - source: hosted - version: "1.1.1+2" - material_ui: - dependency: "direct main" - description: - name: material_ui - sha256: "134ab2f0843545c4e661e4ba2753fe1bcf0753cb690d5a9ee4870638a40411e9" - url: "https://pub.dev" - source: hosted - version: "1.4.0" measure_size: dependency: "direct main" description: @@ -986,18 +931,18 @@ packages: dependency: transitive description: name: meta - sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" mime: dependency: transitive description: name: mime - sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.0.0" motor: dependency: transitive description: @@ -1006,39 +951,39 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "90eaad8059ebc2004a63810a4696ea9acdb02473e47ed3b6b3bafb100ac77e31" - url: "https://pub.dev" - source: hosted - version: "0.19.5" navigation_rail_m3e: dependency: "direct main" description: path: "packages/navigation_rail_m3e" ref: HEAD - resolved-ref: da860a413d5cf78b9c9c967215250bcdbfe5191d + resolved-ref: "667b0bc8526fd53296778903b6ef3f22424f3aa4" url: "https://github.com/Henry-Hiles/material_3_expressive" source: git version: "0.3.5" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" objective_c: dependency: transitive description: name: objective_c - sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3 + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.4.1" package_config: dependency: transitive description: name: package_config - sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.2.0" package_info_plus: dependency: "direct main" description: @@ -1131,10 +1076,10 @@ packages: dependency: transitive description: name: platform - sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1143,54 +1088,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" - url: "https://pub.dev" - source: hosted - version: "3.9.1" pool: dependency: transitive description: name: pool - sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.3" + version: "1.5.2" posix: dependency: transitive description: name: posix - sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" url: "https://pub.dev" source: hosted - version: "6.5.2" - process: - dependency: transitive - description: - name: process - sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891" - url: "https://pub.dev" - source: hosted - version: "5.0.6" + version: "6.5.0" pub_semver: dependency: transitive description: name: pub_semver - sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c38b81cbf34450b67e0265d73433569d12e34782e30ed769c9cc99c9d5f2e796 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.5.0" quiver: dependency: transitive description: @@ -1203,34 +1132,34 @@ packages: dependency: transitive description: name: record_use - sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "0.6.0" riverpod: dependency: transitive description: name: riverpod - sha256: "484dfc873ea4c4f4240e5635444fa066f87b07862b7279ebd004a2b71fba4b7a" + sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76" url: "https://pub.dev" source: hosted - version: "3.4.3" + version: "3.3.2" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: bb13cb3985815840cc103eda1027158358c5fdc00f308fc6f76574d0594107af + sha256: "3e275138862ccc22ed61444a1f9a840f753094c367f28f4123f50289cd204d68" url: "https://pub.dev" source: hosted - version: "1.0.0-dev.12" + version: "1.0.0-dev.10" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "215cb3cd54a97e09bc3512535fa4e8bedd3bf2a06dc2ae015ac8e0050fc71bd1" + sha256: "166f29492228dc471b6fe294092560ccd5545ed15c9fc155622455b58b2451a9" url: "https://pub.dev" source: hosted - version: "3.1.9" + version: "3.1.4" rxdart: dependency: transitive description: @@ -1299,18 +1228,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" url: "https://pub.dev" source: hosted - version: "2.4.28" + version: "2.4.27" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -1351,6 +1280,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: @@ -1368,18 +1313,34 @@ packages: dependency: transitive description: name: source_gen - sha256: "11b6047da8e4eb6c643ccac3d0fda3f9c9e11651695f7da24a85ade8aff15a44" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.2.3" source_helper: dependency: transitive description: name: source_helper - sha256: "5e6f216fdf6376c9f3852381ae037499797a3385377d388b011dac98d303c67c" + sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" url: "https://pub.dev" source: hosted - version: "1.3.13" + version: "1.3.12" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -1392,10 +1353,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.2" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -1416,10 +1377,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -1440,10 +1401,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "397b146f97613b83d84bdccb439bc8d0f3aebb6c1d14a9641e6f24201c490b2d" + sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" url: "https://pub.dev" source: hosted - version: "3.4.2" + version: "3.4.1" term_glyph: dependency: transitive description: @@ -1452,14 +1413,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + url: "https://pub.dev" + source: hosted + version: "1.31.0" test_api: dependency: transitive description: name: test_api - sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.11" + test_core: + dependency: transitive + description: + name: test_core + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + url: "https://pub.dev" + source: hosted + version: "0.6.17" timeago: dependency: "direct main" description: @@ -1468,14 +1445,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.1" - timezone: - dependency: transitive - description: - name: timezone - sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb" - url: "https://pub.dev" - source: hosted - version: "0.11.1" typed_data: dependency: transitive description: @@ -1484,56 +1453,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - unifiedpush: - dependency: "direct main" - description: - name: unifiedpush - sha256: "8ed9767f750a1dc6159a77e2171641d0cb825dc87682d1ce1b8618689b79f58e" - url: "https://pub.dev" - source: hosted - version: "6.2.0" - unifiedpush_android: + universal_html: dependency: transitive description: - name: unifiedpush_android - sha256: a902e505e91c8e28b54b379bb27f255f66df409274a97af24d6719f12f0d13dc + name: universal_html + sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4 url: "https://pub.dev" source: hosted - version: "3.5.0" - unifiedpush_linux: - dependency: "direct overridden" - description: - path: unifiedpush_linux - ref: "quad/feat/should-write-service-option" - resolved-ref: "7589b07ef4280820e4adea165d3d86b219f6022c" - url: "https://codeberg.org/Henry-Hiles/flutter-connector" - source: git - version: "1.0.1" - unifiedpush_platform_interface: - dependency: "direct overridden" - description: - path: unifiedpush_platform_interface - ref: "quad/feat/should-write-service-option" - resolved-ref: "7589b07ef4280820e4adea165d3d86b219f6022c" - url: "https://codeberg.org/Henry-Hiles/flutter-connector" - source: git - version: "4.0.0" - unifiedpush_storage_interface: + version: "2.3.0" + universal_io: dependency: transitive description: - name: unifiedpush_storage_interface - sha256: b8d423a4695efc616aa21d8ab48fb5ef99d6288c68b56282b8faac1579ceabd9 + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 url: "https://pub.dev" source: hosted - version: "1.0.0" - unifiedpush_storage_shared_preferences: - dependency: "direct main" - description: - name: unifiedpush_storage_shared_preferences - sha256: eda9c52bac0058f81e0d9c65e33eedc12c5901d2e68ad47fdf68175ddf00a3b4 - url: "https://pub.dev" - source: hosted - version: "1.0.0" + version: "2.3.1" universal_platform: dependency: transitive description: @@ -1562,34 +1497,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.33" + version: "6.3.32" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a" + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.4.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.6" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1610,10 +1545,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.1.5" uuid: dependency: transitive description: @@ -1626,10 +1561,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: @@ -1642,42 +1577,42 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" + sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.6" vector_math: dependency: transitive description: name: vector_math - sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.3.0" + version: "15.2.0" wakelock_plus: dependency: transitive description: name: wakelock_plus - sha256: "22b3e7e937721de70e63c85e7139f4ac781dc22863b9262431a53ac030eb074b" + sha256: "824c5bba0f800e86d32e57d3d1843c531f090005cc89d9a837933e6601093d53" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.6.1" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "764c25504562abc8ac3406f5d175f126b276e6ba916c7c2675690b73368ca384" + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.5.1" watcher: dependency: transitive description: @@ -1710,30 +1645,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - webcrypto: - dependency: "direct dev" - description: - name: webcrypto - sha256: "6b43001c4110856ff7fa5e5e65e7b2d44bec1d8b54a4d84d5fa2c7622267c5c1" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - webpush_encryption: + webkit_inspection_protocol: dependency: transitive description: - name: webpush_encryption - sha256: "5b83272b91acda6ae515fcd980c94f06bf413702282497c5a68f5dfc64fed27f" + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.2.1" win32: dependency: transitive description: name: win32 - sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.3.0" window_manager: dependency: "direct main" description: @@ -1742,15 +1669,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.2" - xdg_desktop_portal: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "9ead341e908da2972293b2edc345f2b0a6423d78" - url: "https://github.com/Henry-Hiles/xdg_desktop_portal.dart" - source: git - version: "0.1.14" xdg_directories: dependency: "direct main" description: @@ -1763,18 +1681,18 @@ packages: dependency: transitive description: name: xml - sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.6.1" yaml: dependency: transitive description: name: yaml - sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.3" yaml_edit: dependency: transitive description: @@ -1784,5 +1702,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: "3.13.3" - flutter: ">=3.47.0" + dart: ">=3.12.2 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2535e0b..72748b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,119 +1,98 @@ name: nexus -description: "A simple and user-friendly Matrix client" +description: "Yet another Matrix client" version: 0.1.0 publish_to: none flutter: - fonts: - - family: fallback-emoji - fonts: - - asset: assets/fonts/NotoColorEmoji.ttf - - family: fallback-sans - fonts: - - asset: assets/fonts/Roboto.ttf - assets: - - assets/bundled/ - uses-material-design: true + fonts: + - family: fallback-emoji + fonts: + - asset: assets/fonts/NotoColorEmoji.ttf + - family: fallback-sans + fonts: + - asset: assets/fonts/Roboto.ttf + assets: + - assets/ + uses-material-design: true environment: - sdk: 3.13.3 + sdk: "^3.12.2" dependency_overrides: - dynamic_color: 2.1.0 - unifiedpush_linux: - git: - url: https://codeberg.org/Henry-Hiles/flutter-connector - ref: quad/feat/should-write-service-option - path: unifiedpush_linux - unifiedpush_platform_interface: - git: - url: https://codeberg.org/Henry-Hiles/flutter-connector - ref: quad/feat/should-write-service-option - path: unifiedpush_platform_interface - path_provider_android: 2.2.23 # Pinned to avoid JNI - linkify: - git: - url: https://github.com/appelladev/linkify - ref: fix/consecutive-periods-loose-url + path_provider_android: 2.2.23 # Pinned to avoid JNI + linkify: + git: + url: https://github.com/appelladev/linkify + ref: fix/consecutive-periods-loose-url dependencies: - flutter: - sdk: flutter - flutter_localizations: - sdk: flutter - flutter_riverpod: 3.4.3 - hooks_riverpod: 3.4.3 - intl: 0.20.3 - fast_immutable_collections: 11.2.0 - path_provider: 2.1.6 - url_launcher: 6.3.2 - freezed_annotation: 3.1.0 - image_picker: 1.2.3 - path: 1.9.1 - dynamic_color: 2.1.0 - collection: 1.19.1 - window_manager: 0.5.2 - color_hash: 1.0.1 - flutter_widget_from_html_core: 0.17.4 - flutter_svg: 2.3.0 - json_annotation: 4.12.0 - shared_preferences: 2.5.5 - fluttertagger: 2.3.2 - dynamic_polls: 0.0.8 - flutter_hooks: 0.21.3+1 - ffi: 2.2.0 - hooks: 2.2.0 - code_assets: 2.1.0 - ffigen: 22.0.0 - timeago: 3.7.1 - http: 1.6.0 - flutter_linkify: 6.0.0 - linkify: 5.0.0 - flutter_blurhash: 0.9.1 - super_sliver_list: 0.4.1 - media_kit: 1.2.6 - media_kit_video: 2.0.1 - media_kit_libs_video: 1.0.7 - measure_size: 5.0.2 - m3e_buttons: 1.0.3 - navigation_rail_m3e: - git: - url: https://github.com/Henry-Hiles/material_3_expressive - path: packages/navigation_rail_m3e - m3e_card_list: 1.0.0 - xdg_directories: 1.1.0 - package_info_plus: 10.2.1 - app_links: 7.2.1 - file_selector: 1.1.0 - material_ui: 1.4.0 - unifiedpush: 6.2.0 - unifiedpush_storage_shared_preferences: 1.0.0 - flutter_local_notifications: 22.3.1 - material_emoji_picker: 1.1.1+2 - xdg_desktop_portal: - git: - url: https://github.com/Henry-Hiles/xdg_desktop_portal.dart - + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_riverpod: 3.3.2 + hooks_riverpod: 3.3.2 + intl: 0.20.2 + fast_immutable_collections: 11.2.0 + path_provider: 2.1.6 + url_launcher: 6.3.2 + freezed_annotation: 3.1.0 + image_picker: ^1.2.3 + path: 1.9.1 + dynamic_color: 1.8.1 + collection: 1.19.1 + window_manager: 0.5.2 + color_hash: 1.0.1 + flutter_widget_from_html_core: 0.17.2 + flutter_svg: 2.3.0 + json_annotation: 4.12.0 + shared_preferences: 2.5.5 + fluttertagger: 2.3.2 + dynamic_polls: 0.0.7 + flutter_hooks: 0.21.3+1 + ffi: 2.2.0 + hooks: 2.0.2 + code_assets: 1.2.1 + ffigen: 20.1.1 + timeago: 3.7.1 + http: 1.6.0 + flutter_linkify: 6.0.0 + linkify: 5.0.0 + emoji_text_field: + git: + url: https://github.com/Henry-Hiles/emoji_text_field + flutter_blurhash: 0.9.1 + super_sliver_list: 0.4.1 + media_kit: 1.2.6 + media_kit_video: 2.0.1 + media_kit_libs_video: 1.0.7 + measure_size: 5.0.2 + m3e_buttons: 0.0.4 + navigation_rail_m3e: + git: + url: https://github.com/Henry-Hiles/material_3_expressive + path: packages/navigation_rail_m3e + m3e_card_list: 0.1.0 + xdg_directories: 1.1.0 + package_info_plus: 10.2.1 + app_links: 7.2.1 + file_selector: ^1.1.0 + dev_dependencies: - build_runner: 2.16.1 - flutter_lints: 6.0.0 - freezed: 4.0.2 - riverpod_lint: 3.1.9 - flutter_launcher_icons: 0.14.4 - json_serializable: 6.14.1 - # Transitive dep but needed for commands - webcrypto: 0.6.0 + build_runner: ^2.15.1 + flutter_lints: 6.0.0 + freezed: 3.2.6-dev.1 + riverpod_lint: 3.1.4 + flutter_launcher_icons: 0.14.4 + json_serializable: 6.14.0 flutter_launcher_icons: - image_path: assets/icon.png - ios: true - image_path_ios: assets/mobile.png - android: true - adaptive_icon_background: assets/background.png - adaptive_icon_foreground: assets/foreground.png - adaptive_icon_monochrome: assets/monochrome.png - windows: - generate: true - macos: - generate: true - image_path: assets/mobile.png + ios: true + android: true + image_path: assets/icon.png + adaptive_icon_background: assets/background.png + adaptive_icon_foreground: assets/foreground.png + adaptive_icon_monochrome: assets/monochrome.png + remove_alpha_ios: true + windows: + generate: true \ No newline at end of file diff --git a/scripts/generate.dart b/scripts/generate.dart index d8a8cb1..c381230 100644 --- a/scripts/generate.dart +++ b/scripts/generate.dart @@ -1,5 +1,4 @@ import "dart:io"; - import "package:ffigen/ffigen.dart"; import "package:path/path.dart"; import "package:nexus/helpers/extensions/get_xcode_sdk.dart"; @@ -7,23 +6,21 @@ import "package:nexus/helpers/extensions/get_xcode_sdk.dart"; void main(List args) async { final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks")); - print("Generating FFI bindings..."); + print("Generating FFI Bindings..."); final libclangPath = Platform.environment["LIBCLANG_PATH"]; FfiGenerator( - output: .new( - dart: .new( - path: Platform.script.resolve("../lib/src/third_party/gomuks.g.dart"), - ), + output: Output( + dartFile: Platform.script.resolve("../lib/src/third_party/gomuks.g.dart"), ), - visitors: [.new(func: (node) => node.isIncluded = true)], - input: .new( + headers: Headers( entryPoints: [File(join(repoDir.path, "pkg", "ffi", "gomuksffi.h")).uri], compilerOptions: [ "--no-warnings", - if (Platform.isMacOS) "-I${await getXCodeTool()}/usr/include", + if (Platform.isMacOS) "-I${await getXCodeSDK()}/usr/include", ], ), + functions: Functions.includeAll, ).generate( libclangDylib: libclangPath == null ? null @@ -40,5 +37,5 @@ void main(List args) async { ), ), ); - print("FFI bindings generated!"); + print("Done!"); } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 5d2b53e..0694802 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -31,8 +30,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); - WebcryptoPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("WebcryptoPlugin")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index c74b2f5..238ef93 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -10,12 +10,10 @@ list(APPEND FLUTTER_PLUGIN_LIST media_kit_video screen_retriever_windows url_launcher_windows - webcrypto window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - flutter_local_notifications_windows ) set(PLUGIN_BUNDLED_LIBRARIES)