diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index dc1e9c7..a3abae1 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 --release" + 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" env: KEYSTORE_PATH: ../../keystore.jks KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 0000000..e14f1bb --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,48 @@ +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.0 + + - 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 new file mode 100644 index 0000000..53c6746 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,47 @@ +name: "Build MacOS 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.0 + + - 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 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 }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d9c8dd5..3ef3552 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,65 +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.44.4 + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 3.47.0 - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: gomuks/go.mod + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: gomuks/go.mod - - name: Setup MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - install: >- - mingw-w64-x86_64-gcc + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: >- + mingw-w64-x86_64-gcc - - name: Go build - run: | - cd gomuks/pkg/ffi - go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared + - name: Go build + run: | + cd gomuks/pkg/ffi + go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared - - name: Build with Flutter - run: | - flutter pub get - dart scripts/generate.dart - flutter pub run build_runner build - flutter build windows --release + - name: Build with Flutter + run: | + flutter pub get + dart scripts/generate.dart + flutter pub run build_runner build + flutter build windows --release - - 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: 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: Install Inno Setup - run: choco install innosetup -y + - name: Install Inno Setup + run: choco install innosetup -y - - name: Build Inno Setup installer - run: iscc windows/installer.iss + - 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 + - name: Upload installer artifact + uses: actions/upload-artifact@v6 + with: + name: windows-installer + path: windows/dist/Nexus-Setup.exe diff --git a/.gitmodules b/.gitmodules index dc5e02e..145276a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "gomuks"] path = gomuks - url = https://github.com/henry-hiles/gomuks + url = https://github.com/gomuks/gomuks branch = main diff --git a/.metadata b/.metadata index 1be1841..f12453b 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "nixpkgs000000000000000000000000000000000" + revision: "6b182d2c7585eba26d4edce0f97630effd256c33" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: nixpkgs000000000000000000000000000000000 - base_revision: nixpkgs000000000000000000000000000000000 - - platform: macos - create_revision: nixpkgs000000000000000000000000000000000 - base_revision: nixpkgs000000000000000000000000000000000 + create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 + base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 + - platform: ios + create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 + base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33 # User provided section diff --git a/README.md b/README.md index d191d6e..0c18acf 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,12 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. ## Progress -- [ ] Platform Support +- [x] Platform Support - [x] Linux - - [x] Windows + - [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 - - [ ] MacOS ([#50](https://git.federated.nexus/Nexus/nexus/issues/50)) - - [ ] iOS + - [x] MacOS - Unsigned .dmg only + - [x] iOS - Unsigned .ipa only - [ ] Web (may not be possible) - [x] Login (via OAuth) - [x] Rooms / Spaces @@ -116,6 +116,9 @@ 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) - [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.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) @@ -126,16 +129,17 @@ If you want to try out Nexus, grab one of the following artifacts from CI: #### Linux -- With Nix: Either use direnv and `direnv allow`, or `nix flake develop` -- Without Nix: Install Flutter, Go, Git, Libclang, and Glibc. Do not use any Snap packages, they cause various compilation issues. +- 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. #### Windows You will need: - Flutter -- Android SDK + NDK - Git +- Libass +- MPV - Go - Visual Studio 2022 (Desktop development with C++) - [MSYS2/MinGW-w64 GCC](https://www.msys2.org/) (for CGO) @@ -202,7 +206,7 @@ Development instructions can be found in [DEVELOPMENT.md](./DEVELOPMENT.md). ## Community -Join the [Nexus Client Matrix Room](https://matrix.to/#/#nexus:federated.nexus) for questions or help with developing or using Nexus Client. +Join the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus) for questions or help with developing or using Nexus Client. # Credits diff --git a/altstore-source.json b/altstore-source.json new file mode 100644 index 0000000..4471fb6 --- /dev/null +++ b/altstore-source.json @@ -0,0 +1,22 @@ +{ + "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 a8b1078..17adc10 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,12 +1,19 @@ analyzer: - errors: - invalid_annotation_target: ignore - avoid_print: ignore - exclude: - - "build/**" - - "**/*.g.dart" - - "**/*.freezed.dart" + errors: + invalid_annotation_target: ignore + avoid_print: ignore + annotate_overrides: ignore + exclude: + - "build/**" + - "**/*.g.dart" + - "**/*.freezed.dart" + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** include: package:flutter_lints/flutter.yaml linter: - rules: - prefer_double_quotes: true \ No newline at end of file + rules: + prefer_double_quotes: true diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 2e7fb67..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -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 new file mode 100644 index 0000000..8a23bb6 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,74 @@ +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 { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + defaultConfig { + applicationId = "nexus.federated.nexus" + minSdk = 29 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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" + } + } +} + +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 deleted file mode 100644 index 11bc662..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -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 new file mode 100644 index 0000000..441eb71 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,25 @@ +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 74b269f..76d4386d 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-8.14.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 7cffcd0..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -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 new file mode 100644 index 0000000..b59bc94 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +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/icon.svg b/assets/bundled/icon.svg similarity index 100% rename from assets/icon.svg rename to assets/bundled/icon.svg diff --git a/flake.lock b/flake.lock index 41cfb9d..3544dd7 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1782949081, - "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "lastModified": 1785627969, + "narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a", "type": "github" }, "original": { @@ -73,11 +73,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1782614948, - "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", + "lastModified": 1785031560, + "narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", + "rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c", "type": "github" }, "original": { @@ -88,11 +88,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1784497964, - "narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=", + "lastModified": 1786862985, + "narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163", + "rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44", "type": "github" }, "original": { diff --git a/gomuks b/gomuks index 1f11743..9444dd2 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit 1f11743884a64765b1444b250377a09ebcaba93c +Subproject commit 9444dd293664c300c6e710bb6cddcd9c6cab365d diff --git a/hook/build.dart b/hook/build.dart index 625201d..9315e04 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,4 +1,5 @@ import "dart:io"; + import "package:collection/collection.dart"; import "package:hooks/hooks.dart"; import "package:code_assets/code_assets.dart"; @@ -13,22 +14,46 @@ 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 getXCodeSDK(), - "CGO_ENABLED": "1", - "GOARCH": switch (targetArch) { - Architecture.arm64 => "arm64", - Architecture.x64 => "amd64", - _ => throw UnsupportedError( - "Unsupported macOS architecture: $targetArch", - ), - }, + "SDKROOT": await getXCodeTool(), + "MACOSX_DEPLOYMENT_TARGET": codeConfig.macOS.targetVersion.toString(), }; break; case OS.windows: @@ -96,15 +121,28 @@ Future main(List args) => build(args, (input, output) async { final tags = [ "sqlite_fts5", "goolm", - // goheif/dav1d is not supported on Android, would need to be fixed upstream - if (targetOS == OS.android) "noheic", + // goheif/dav1d is not supported on Android or iOS, would need to be fixed upstream + if (targetOS == OS.android || targetOS == OS.iOS) "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 ${libFile.path}...", + "Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) to ${archiveFile.path}...", ); final result = await Process.run( "go", - ["build", "-tags", tags, "-o", libFile.path, "-buildmode=c-shared"], + [ + "build", + "-trimpath", + "-ldflags=-s -w", + "-tags", + tags, + "-o", + archiveFile.path, + "-buildmode=${targetOS == OS.iOS ? "c-archive" : "c-shared"}", + ], workingDirectory: gomuksBuildDir.resolve("pkg/ffi/").toFilePath(), environment: { "CGO_ENABLED": "1", @@ -124,6 +162,35 @@ 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 new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/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 new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + 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 new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#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 new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# 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 new file mode 100644 index 0000000..b29d5cb --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..065d08b --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,759 @@ +// !$*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 new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + 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 new file mode 100644 index 0000000..d0d98aa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..1682af0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png 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 new file mode 100644 index 0000000..51f4a9b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png 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 new file mode 100644 index 0000000..97b3b3f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png 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 new file mode 100644 index 0000000..17d971e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png 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 new file mode 100644 index 0000000..ca74554 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png 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 new file mode 100644 index 0000000..6129702 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png 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 new file mode 100644 index 0000000..16c3d1e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png 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 new file mode 100644 index 0000000..97b3b3f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png 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 new file mode 100644 index 0000000..f515ebd Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png 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 new file mode 100644 index 0000000..2e49a13 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png 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 new file mode 100644 index 0000000..66b9ef2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png 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 new file mode 100644 index 0000000..53af9ff Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png 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 new file mode 100644 index 0000000..455803d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png 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 new file mode 100644 index 0000000..7a2fd0f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png 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 new file mode 100644 index 0000000..2e49a13 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png 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 new file mode 100644 index 0000000..c34ad9e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png 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 new file mode 100644 index 0000000..5e21545 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png 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 new file mode 100644 index 0000000..2140e17 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png 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 new file mode 100644 index 0000000..a27d854 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png 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 new file mode 100644 index 0000000..8b4eb75 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png 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 new file mode 100644 index 0000000..21c424b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "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 new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# 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 new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..7680b42 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,79 @@ + + + + + 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 new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +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/account_data.dart b/lib/controllers/account_data.dart index 0926e08..c595565 100644 --- a/lib/controllers/account_data.dart +++ b/lib/controllers/account_data.dart @@ -1,16 +1,22 @@ +import "dart:convert"; + import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/models/account_data.dart"; -class AccountDataController extends Notifier> { +class AccountDataController extends Notifier { @override - IMap build() => .new(); + AccountData build() => .new(); - void update(IMap newData) => - state = .new({...state.unlock, ...newData.unlock}); + void update(IMap> newAccountData) => + state = .fromJson({ + ...json.decode(json.encode(state.toJson())), + ...newAccountData + .map((key, value) => MapEntry(key, value["content"])) + .unlock, + }); - static final provider = - NotifierProvider>( - AccountDataController.new, - ); + static final provider = NotifierProvider( + AccountDataController.new, + ); } diff --git a/lib/controllers/attachment.dart b/lib/controllers/attachment.dart index e33493e..d4a5e09 100644 --- a/lib/controllers/attachment.dart +++ b/lib/controllers/attachment.dart @@ -6,10 +6,8 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/message.dart"; import "package:path/path.dart"; -class AttachmentController extends Notifier<(String, MessageContent?)?> { - final String roomId; - AttachmentController(this.roomId); - +class AttachmentController(final String roomId) + extends Notifier<(String, MessageContent?)?> { @override Null build() => null; @@ -17,18 +15,16 @@ class AttachmentController extends Notifier<(String, MessageContent?)?> { final filename = basename(file.path); state = (filename, null); - final room = ref.watch( - RoomsController.provider.select((value) => value[roomId]), + final isEncrypted = ref.read( + RoomsController.provider.select( + (value) => + value[roomId]?.state[EventType.encryption.type]?.isNotEmpty == true, + ), ); final content = await ref .watch(ClientController.provider.notifier) - .uploadMedia( - .new( - path: file.path, - encrypt: room?.state[EventType.encryption.type]?.isNotEmpty == true, - ), - ); + .uploadMedia(.new(path: file.path, encrypt: isEncrypted)); state = (filename, content); } diff --git a/lib/controllers/auth_url.dart b/lib/controllers/auth_url.dart index 156eb81..963df86 100644 --- a/lib/controllers/auth_url.dart +++ b/lib/controllers/auth_url.dart @@ -4,10 +4,8 @@ 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 extends AsyncNotifier { - final Uri homeserver; - AuthUrlController(this.homeserver); - +class AuthUrlController(final Uri homeserver) + extends AsyncNotifier { @override Future build() async => ref .watch(ClientController.provider.notifier) diff --git a/lib/controllers/author.dart b/lib/controllers/author.dart index bbbe068..f9c9167 100644 --- a/lib/controllers/author.dart +++ b/lib/controllers/author.dart @@ -1,19 +1,17 @@ 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 extends AsyncNotifier { - final Event event; - AuthorController(this.event); - +class AuthorController(final Event event) + extends AsyncNotifier { @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 2cb1d66..b7d1eb4 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -17,13 +17,14 @@ import "package:nexus/main.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.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/requests/download_media.dart"; import "package:nexus/models/requests/get_event.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"; -import "package:nexus/models/profile.dart"; +import "package:nexus/models/profile_response.dart"; 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"; @@ -32,10 +33,13 @@ import "package:nexus/models/requests/redact_event.dart"; import "package:nexus/models/requests/report.dart"; import "package:nexus/models/requests/send_event.dart"; import "package:nexus/models/requests/send_message.dart"; +import "package:nexus/models/requests/set_account_data.dart"; 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_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"; @@ -45,7 +49,7 @@ class ClientController extends AsyncNotifier { @override Future build() async { final Pointer root; - if (Platform.isAndroid) { + if (Platform.isAndroid || Platform.isIOS) { final dir = await getApplicationSupportDirectory(); root = "${dir.path}/gomuks".toNativeUtf8().cast(); } else { @@ -210,15 +214,11 @@ class ClientController extends AsyncNotifier { } } - Future joinRoom(JoinRoomRequest request) async { - final response = await _sendCommand("join_room", request.toJson()); - return response["room_id"]; - } + Future joinRoom(JoinRoomRequest request) async => + (await _sendCommand("join_room", request.toJson()))["room_id"]; - Future getAccessToken() async { - final response = await _sendCommand("get_account_info", {}); - return response?["access_token"]; - } + Future getRoomSummary(JoinRoomRequest request) async => + .fromJson(await _sendCommand("get_room_summary", request.toJson())); Future leaveRoom(Room room) async { if (room.metadata == null) return; @@ -255,13 +255,14 @@ class ClientController extends AsyncNotifier { return json == null ? null : .fromJson(json); } + Future getUrlPreview(Uri url) async => + .fromJson(await _sendCommand("get_url_preview", {"url": url.toString()})); + Future paginate(PaginateRequest request) async => .fromJson(await _sendCommand("paginate", request.toJson())); - Future getProfile(String userId) async { - final json = await _sendCommand("get_profile", {"user_id": userId}); - return .fromJsonWithCatch({...json, "id": userId}); - } + Future getProfile(String userId) async => + .fromJson(await _sendCommand("get_profile", {"user_id": userId})); Future reportEvent(ReportRequest request) => _sendCommand("report_event", request.toJson()); @@ -269,6 +270,9 @@ class ClientController extends AsyncNotifier { Future setMembership(SetMembershipRequest request) => _sendCommand("set_membership", request.toJson()); + Future setAccountData(SetAccountDataRequest request) => + _sendCommand("set_account_data", request.toJson()); + Future uploadMedia(UploadMediaRequest request) async => .fromJson(await _sendCommand("upload_media", request.toJson())); @@ -303,10 +307,13 @@ class ClientController extends AsyncNotifier { Future exchangeToken(OAuthExchangeTokenRequest request) async => await _sendCommand("oauth_exchange_token", request.toJson()); + Future getSpecVersions() async => + .fromJson(await _sendCommand("get_versions")); + Future discoverHomeserver(Uri homeserver) async { try { final response = await _sendCommand("discover_homeserver", { - "user_id": "@fake-user:${homeserver.host}", + "user_id": "@fake-user:${homeserver.authority}", }); 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 ac8f5cb..7748f97 100644 --- a/lib/controllers/client_id.dart +++ b/lib/controllers/client_id.dart @@ -1,10 +1,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; -class ClientIdController extends AsyncNotifier { - final Uri homeserver; - ClientIdController(this.homeserver); - +class ClientIdController(final Uri homeserver) extends AsyncNotifier { @override Future build() => ref .watch(ClientController.provider.notifier) diff --git a/lib/controllers/emoji.dart b/lib/controllers/emoji.dart index caea3de..b0c1bd3 100644 --- a/lib/controllers/emoji.dart +++ b/lib/controllers/emoji.dart @@ -1,7 +1,7 @@ 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:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:http/http.dart"; import "package:nexus/models/emoji.dart"; diff --git a/lib/controllers/event.dart b/lib/controllers/event.dart index b7b417a..7a1e5dc 100644 --- a/lib/controllers/event.dart +++ b/lib/controllers/event.dart @@ -5,10 +5,8 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/get_event.dart"; -class EventController extends AsyncNotifier { - final GetEventRequest request; - EventController(this.request); - +class EventController(final GetEventRequest request) + extends AsyncNotifier { @override Future build() async { final room = ref.watch( diff --git a/lib/controllers/header.dart b/lib/controllers/header.dart deleted file mode 100644 index 3e173ad..0000000 --- a/lib/controllers/header.dart +++ /dev/null @@ -1,20 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; - -class HeaderController extends AsyncNotifier> { - @override - Future> build() async { - if (ref.watch(ClientStateController.provider)?.isLoggedIn != true) { - return {}; - } - final client = ref.watch(ClientController.provider.notifier); - final accessToken = await client.getAccessToken(); - return {"authorization": "Bearer $accessToken"}; - } - - static final provider = - AsyncNotifierProvider>( - HeaderController.new, - ); -} diff --git a/lib/controllers/key.dart b/lib/controllers/key.dart index eff3ab2..b78acf8 100644 --- a/lib/controllers/key.dart +++ b/lib/controllers/key.dart @@ -1,10 +1,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/shared_prefs.dart"; -class KeyController extends Notifier { - final String key; - KeyController(this.key); - +class KeyController(final String key) extends Notifier { static const String spaceKey = "space"; static const String roomKey = "room"; diff --git a/lib/controllers/member_list_opened.dart b/lib/controllers/member_list_opened.dart new file mode 100644 index 0000000..e3509f0 --- /dev/null +++ b/lib/controllers/member_list_opened.dart @@ -0,0 +1,22 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/shared_prefs.dart"; + +class MemberListOpenedController extends Notifier { + static const String key = "memberListOpened"; + + @override + bool build() => + ref.watch(SharedPrefsController.provider).requireValue.getBool(key) ?? + true; + + Future set(bool value) async { + final prefs = ref.watch(SharedPrefsController.provider).requireValue; + state = value; + + prefs.setBool(key, value); + } + + static final provider = NotifierProvider( + MemberListOpenedController.new, + ); +} diff --git a/lib/controllers/members.dart b/lib/controllers/members.dart index 8566b40..3c287ea 100644 --- a/lib/controllers/members.dart +++ b/lib/controllers/members.dart @@ -6,10 +6,8 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/get_room_state.dart"; -class MembersController extends AsyncNotifier> { - final String roomId; - MembersController(this.roomId); - +class MembersController(final String roomId) + extends AsyncNotifier> { @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 613f7b1..82905aa 100644 --- a/lib/controllers/members_by_status.dart +++ b/lib/controllers/members_by_status.dart @@ -5,10 +5,8 @@ import "package:nexus/models/configs/members_by_status.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/event.dart"; -class MembersByStatusController extends AsyncNotifier> { - final MembersByStatusConfig config; - MembersByStatusController(this.config); - +class MembersByStatusController(final MembersByStatusConfig config) + extends AsyncNotifier> { @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 6f41e9d..f903ec4 100644 --- a/lib/controllers/members_grouped.dart +++ b/lib/controllers/members_grouped.dart @@ -8,11 +8,8 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/event.dart"; -class MembersGroupedController +class MembersGroupedController(final MembersByStatusConfig config) 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 52dd8d9..f1ae78c 100644 --- a/lib/controllers/multi_provider.dart +++ b/lib/controllers/multi_provider.dart @@ -1,11 +1,10 @@ import "dart:async"; + import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -class MultiProviderController extends AsyncNotifier { - MultiProviderController(this.providers); - final IList providers; - +class MultiProviderController(final IList providers) + extends AsyncNotifier { @override Future build() => .wait(providers.map((provider) => ref.watch(provider.future))); diff --git a/lib/controllers/pinned_events.dart b/lib/controllers/pinned_events.dart index 914a301..323a275 100644 --- a/lib/controllers/pinned_events.dart +++ b/lib/controllers/pinned_events.dart @@ -4,10 +4,8 @@ import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/pinned_ids.dart"; import "package:nexus/models/event.dart"; -class PinnedEventsController extends AsyncNotifier> { - final String roomId; - PinnedEventsController(this.roomId); - +class PinnedEventsController(final String roomId) + extends AsyncNotifier> { @override Future> build() async { final pinIds = ref.watch(PinnedIdsController.provider(roomId)); @@ -15,9 +13,8 @@ class PinnedEventsController extends AsyncNotifier> { 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 3d1e5da..01358a9 100644 --- a/lib/controllers/pinned_ids.dart +++ b/lib/controllers/pinned_ids.dart @@ -5,10 +5,7 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/pinned_events.dart"; -class PinnedIdsController extends Notifier> { - final String roomId; - PinnedIdsController(this.roomId); - +class PinnedIdsController(final String roomId) extends Notifier> { @override IList build() { final room = ref.watch( diff --git a/lib/controllers/power_level.dart b/lib/controllers/power_level.dart index 2f0c72e..7111e38 100644 --- a/lib/controllers/power_level.dart +++ b/lib/controllers/power_level.dart @@ -6,10 +6,8 @@ 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 extends Notifier { - final PowerLevelConfig config; - PowerLevelController(this.config); - +class PowerLevelController(final PowerLevelConfig config) + extends Notifier { @override bool build() { if (config case EventPowerLevelConfig(:final eventType)) { diff --git a/lib/controllers/profile.dart b/lib/controllers/profile.dart index 32525a3..6f0291d 100644 --- a/lib/controllers/profile.dart +++ b/lib/controllers/profile.dart @@ -1,17 +1,17 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; -import "package:nexus/models/profile.dart"; - -class ProfileController extends AsyncNotifier { - final String userId; - ProfileController(this.userId); +import "package:nexus/models/profile_response.dart"; +class ProfileController(final String userId) + extends AsyncNotifier { @override - Future build() { + Future build() { final client = ref.watch(ClientController.provider.notifier); return client.getProfile(userId); } static final provider = AsyncNotifierProvider.family - .autoDispose(ProfileController.new); + .autoDispose( + ProfileController.new, + ); } diff --git a/lib/controllers/reactions.dart b/lib/controllers/reactions.dart index 615a59d..9a92843 100644 --- a/lib/controllers/reactions.dart +++ b/lib/controllers/reactions.dart @@ -5,10 +5,8 @@ import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/configs/reactions.dart"; import "package:nexus/models/content/reaction.dart"; -class ReactionsController extends AsyncNotifier>> { - final ReactionsConfig config; - ReactionsController(this.config); - +class ReactionsController(final ReactionsConfig config) + extends AsyncNotifier>> { @override Future>> build() async { final eventInfo = ref.watch( diff --git a/lib/controllers/room_chat.dart b/lib/controllers/room_chat.dart index 558817c..0cd1011 100644 --- a/lib/controllers/room_chat.dart +++ b/lib/controllers/room_chat.dart @@ -1,5 +1,6 @@ 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"; @@ -15,10 +16,8 @@ import "package:nexus/models/relation_type.dart"; import "package:nexus/models/requests/send_message.dart"; import "package:nexus/models/room.dart"; -class RoomChatController extends AsyncNotifier?> { - final String roomId; - RoomChatController(this.roomId); - +class RoomChatController(final String roomId) + extends AsyncNotifier?> { @override Future?> build() async { final client = ref.watch(ClientController.provider.notifier); @@ -75,12 +74,13 @@ class RoomChatController extends AsyncNotifier?> { ); Future loadOlder() async { + state = AsyncLoading(); final timelineKeys = ref .read(RoomsController.provider.select((value) => value[roomId])) ?.timeline .keys; final response = await ref - .watch(ClientController.provider.notifier) + .read(ClientController.provider.notifier) .paginate( .new( roomId: roomId, @@ -90,26 +90,30 @@ class RoomChatController extends AsyncNotifier?> { ), ); - ref - .watch(RoomsController.provider.notifier) - .update( - IMap({ - roomId: Room( - events: IMap.fromIterable( - response.events.addAll(response.relatedEvents), - keyMapper: (event) => event.rowId, - valueMapper: (event) => event, + if (response.events.isEmpty) { + state = .data(state.value); + } else { + ref + .read(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, + ), ), - hasMore: response.hasMore, - timeline: IMap.fromIterable( - response.events, - keyMapper: (event) => event.timelineRowId, - valueMapper: (event) => event.rowId, - ), - ), - }), - .new(), - ); + }), + .new(), + ); + } return response.hasMore; } @@ -125,11 +129,9 @@ class RoomChatController extends AsyncNotifier?> { if (relationType == .edit) { baseContent = relation?.content; } else { - final attachment = ref.watch(AttachmentController.provider(roomId)); - if (attachment != null && attachment.$2 == null) return; - - baseContent = attachment?.$2; - ref.invalidate(AttachmentController.provider(roomId)); + final provider = AttachmentController.provider(roomId); + baseContent = ref.watch(provider)?.$2; + ref.invalidate(provider); } var taggedMessage = text; diff --git a/lib/controllers/room_creators.dart b/lib/controllers/room_creators.dart index 7db72c2..09acdd7 100644 --- a/lib/controllers/room_creators.dart +++ b/lib/controllers/room_creators.dart @@ -4,10 +4,7 @@ import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/create.dart"; import "package:nexus/models/room.dart"; -class RoomCreatorsController extends Notifier> { - final Room room; - RoomCreatorsController(this.room); - +class RoomCreatorsController(final Room room) extends Notifier> { @override IList build() { final createRowId = room.state[EventType.create.type]?[""]; diff --git a/lib/controllers/room_summary.dart b/lib/controllers/room_summary.dart new file mode 100644 index 0000000..76271a4 --- /dev/null +++ b/lib/controllers/room_summary.dart @@ -0,0 +1,16 @@ +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.watch(ClientController.provider.notifier).getRoomSummary(request); + + static final provider = AsyncNotifierProvider.family + .autoDispose( + RoomSummaryController.new, + ); +} diff --git a/lib/controllers/settings.dart b/lib/controllers/settings.dart index 572d2e9..8c107e3 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 Settings.fromJson(json.decode(await file.readAsString())); + return .fromJson(json.decode(await file.readAsString())); } catch (_) { - return Settings(); + return .new(); } } Future set(Settings settings) async { - state = AsyncData(settings); + state = .data(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 7c9796b..042b42b 100644 --- a/lib/controllers/settings_sections.dart +++ b/lib/controllers/settings_sections.dart @@ -1,12 +1,16 @@ import "dart:io"; + import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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/settings.dart"; +import "package:nexus/controllers/spec_versions.dart"; +import "package:nexus/models/account_data.dart"; import "package:nexus/models/settings_category.dart"; import "package:nexus/main.dart"; import "package:nexus/widgets/settings/dialog_list_tile.dart"; @@ -44,8 +48,7 @@ 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), @@ -71,8 +74,7 @@ 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), @@ -93,6 +95,62 @@ class SettingsSectionsController if (ref.watch(ClientStateController.provider)?.isLoggedIn == true) "Account": .new([ .new(title: "Profile", icon: Icons.person, settings: .new([])), + .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).", + 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, + ), + ); + }, + ), + icon: Icons.person_off, + ), + ]), + ), .new( title: "Other", icon: Icons.key, @@ -106,9 +164,8 @@ class SettingsSectionsController final colorScheme = Theme.of(context).colorScheme; return M3EButton.icon( onPressed: () async { - Navigator.of( - context, - ).popUntil((route) => route.isFirst); + Navigator.of(context) + .popUntil((route) => route.isFirst); await WidgetsBinding.instance.endOfFrame; diff --git a/lib/controllers/spaces.dart b/lib/controllers/spaces.dart index 24113ad..7dc14b0 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/account_data.dart"; import "package:nexus/controllers/rooms.dart"; @@ -84,9 +84,7 @@ class SpacesController extends Notifier> { ], }.nonNulls.toISet(); - final directMessages = IMap( - accountData["m.direct"]?.content ?? {}, - ).values.expand((e) => e).toISet(); + final directMessages = accountData.directMessages.values.flattened; final otherRooms = rooms.entries .where( diff --git a/lib/controllers/spec_versions.dart b/lib/controllers/spec_versions.dart new file mode 100644 index 0000000..1f879bd --- /dev/null +++ b/lib/controllers/spec_versions.dart @@ -0,0 +1,15 @@ +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.watch(ClientController.provider.notifier).getSpecVersions(); + + static final provider = + AsyncNotifierProvider.autoDispose< + SpecVersionsController, + SpecVersionsResponse + >(SpecVersionsController.new); +} diff --git a/lib/controllers/url_preview.dart b/lib/controllers/url_preview.dart index 2a81a3c..41c3ef4 100644 --- a/lib/controllers/url_preview.dart +++ b/lib/controllers/url_preview.dart @@ -1,50 +1,26 @@ -import "dart:convert"; +import "package:flutter/widgets.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:http/http.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/header.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/open_graph_data.dart"; -class UrlPreviewController extends AsyncNotifier { - final String link; - UrlPreviewController(this.link); - +class UrlPreviewController(final Uri url) + extends AsyncNotifier { @override Future build() async { + if (url.host == "matrix.to") return null; + try { - final homeserver = ref.watch( - ClientStateController.provider.select((value) => value?.homeserverUrl), - ); - - if (homeserver != null && !link.contains("matrix.to")) { - { - final response = await get( - .parse(homeserver) - .resolve("/_matrix/client/v1/media/preview_url") - .replace(queryParameters: {"url": link}), - headers: await ref.watch(HeaderController.provider.future), - ); - - if (response.statusCode == 200) { - final decodedValue = json.decode(response.body); - if (decodedValue is! Map) return null; - - return .fromJson( - decodedValue, - ).copyWith(imageUrl: decodedValue["og:image"]); - } - } - } - } catch (_) { + return await ref + .watch(ClientController.provider.notifier) + .getUrlPreview(url); + } catch (error, stackTrace) { + debugPrintStack(label: error.toString(), stackTrace: stackTrace); return null; } - return null; } static final provider = - AsyncNotifierProvider.family< - UrlPreviewController, - OpenGraphData?, - String - >(UrlPreviewController.new); + AsyncNotifierProvider.family( + UrlPreviewController.new, + ); } diff --git a/lib/controllers/user.dart b/lib/controllers/user.dart index b366c88..c69ebd0 100644 --- a/lib/controllers/user.dart +++ b/lib/controllers/user.dart @@ -27,14 +27,15 @@ class UserController extends AsyncNotifier { return content; } - final profile = await ref.watch( + final profileResponse = await ref.watch( ProfileController.provider(config.userId).future, ); return .new( status: .leave, - avatarUrl: profile.avatarUrl, - displayName: profile.displayName ?? config.userId.localpart, + avatarUrl: profileResponse.profile.avatarUrl, + displayName: + profileResponse.profile.displayName ?? config.userId.localpart, ); } diff --git a/lib/controllers/via.dart b/lib/controllers/via.dart index d9227ba..39db682 100644 --- a/lib/controllers/via.dart +++ b/lib/controllers/via.dart @@ -7,10 +7,7 @@ import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/room.dart"; -class ViaController extends Notifier { - final Room room; - ViaController(this.room); - +class ViaController(final Room room) extends Notifier { @override String build() { final servers = {}; diff --git a/lib/helpers/extensions/get_headers.dart b/lib/helpers/extensions/get_headers.dart deleted file mode 100644 index 0fcec48..0000000 --- a/lib/helpers/extensions/get_headers.dart +++ /dev/null @@ -1,7 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/header.dart"; - -extension GetHeaders on WidgetRef { - Map get headers => - watch(HeaderController.provider).requireValue; -} diff --git a/lib/helpers/extensions/get_xcode_sdk.dart b/lib/helpers/extensions/get_xcode_sdk.dart index 4c09246..58920c8 100644 --- a/lib/helpers/extensions/get_xcode_sdk.dart +++ b/lib/helpers/extensions/get_xcode_sdk.dart @@ -1,11 +1,14 @@ import "dart:io"; -Future getXCodeSDK() async { - final result = await Process.run("xcrun", ["--show-sdk-path"]); +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", + ]); if (result.exitCode != 0) { - throw Exception("Failed to get XCode SDK\n${result.stderr}"); + throw Exception("Failed to get ${sdkType ?? "XCode"} ${findTool ?? "SDK"}"); } - return result.stdout.trim(); + return result.stdout.toString().trim(); } diff --git a/lib/helpers/extensions/link_to_mention.dart b/lib/helpers/extensions/link_to_mention.dart index f4868d3..8595669 100644 --- a/lib/helpers/extensions/link_to_mention.dart +++ b/lib/helpers/extensions/link_to_mention.dart @@ -9,6 +9,7 @@ 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 bf48535..6beca04 100644 --- a/lib/helpers/extensions/scheme_to_theme.dart +++ b/lib/helpers/extensions/scheme_to_theme.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 2f24a3e..1d20315 100644 --- a/lib/helpers/extensions/show_about_dialog.dart +++ b/lib/helpers/extensions/show_about_dialog.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_card_list/m3e_card_list.dart"; @@ -20,7 +20,7 @@ extension ShowContextMenu on BuildContext { Row( spacing: 12, children: [ - SvgPicture.asset("assets/icon.svg", width: 64), + SvgPicture.asset("assets/bundled/icon.svg", width: 64), Expanded( child: Column( crossAxisAlignment: .start, diff --git a/lib/helpers/extensions/show_context_menu.dart b/lib/helpers/extensions/show_context_menu.dart index c860115..589942b 100644 --- a/lib/helpers/extensions/show_context_menu.dart +++ b/lib/helpers/extensions/show_context_menu.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; extension ShowContextMenu on BuildContext { void showContextMenu({ diff --git a/lib/helpers/extensions/show_user_popover.dart b/lib/helpers/extensions/show_user_popover.dart index 1ea3015..f42cd57 100644 --- a/lib/helpers/extensions/show_user_popover.dart +++ b/lib/helpers/extensions/show_user_popover.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 eaa7714..a21ca82 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; extension ToColor on String { Color get colorHash => ColorHash(this, lightness: .5, saturation: .7).color; diff --git a/lib/helpers/launch_helper.dart b/lib/helpers/launch_helper.dart index 575395f..89c7015 100644 --- a/lib/helpers/launch_helper.dart +++ b/lib/helpers/launch_helper.dart @@ -2,10 +2,7 @@ import "package:flutter/services.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:url_launcher/url_launcher.dart" as ul; -class LaunchHelper { - final Ref ref; - LaunchHelper(this.ref); - +class LaunchHelper(Ref 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 3adb10e..83a345a 100644 --- a/lib/helpers/mxc_image.dart +++ b/lib/helpers/mxc_image.dart @@ -1,14 +1,12 @@ 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 extends ImageProvider { - final WidgetRef ref; - final DownloadMediaRequest request; - const MxcImage(this.ref, this.request); - +class MxcImage(final WidgetRef ref, final DownloadMediaRequest request) + extends ImageProvider { @override Future obtainKey(ImageConfiguration configuration) => Future.value(this); diff --git a/lib/main.dart b/lib/main.dart index f50d22d..a87d7f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import "dart:io"; + import "package:dynamic_color/dynamic_color.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/foundation.dart"; @@ -6,7 +7,6 @@ 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/header.dart"; import "package:nexus/controllers/multi_provider.dart"; import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/shared_prefs.dart"; @@ -19,7 +19,7 @@ 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; final GlobalKey navigatorKey = GlobalKey(); @@ -95,16 +95,13 @@ void main() async { ); } -class App extends StatelessWidget { - const App({super.key}); - +class const App({super.key}) extends StatelessWidget { @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) @@ -113,7 +110,7 @@ class App extends StatelessWidget { data: (settings) => settings.useDynamicTheming ? lightDynamic : null, ) ?? - ColorScheme.fromSeed(seedColor: Colors.indigo)) + ThemeData.light().colorScheme) .theme, darkTheme: (ref @@ -123,10 +120,7 @@ class App extends StatelessWidget { data: (settings) => settings.useDynamicTheming ? darkDynamic : null, ) ?? - ColorScheme.fromSeed( - seedColor: Colors.indigo, - brightness: Brightness.dark, - )) + ThemeData.dark().colorScheme) .theme, themeMode: ref .watch(SettingsController.provider) @@ -144,7 +138,6 @@ class App extends StatelessWidget { IListConst([ SharedPrefsController.provider, ClientController.provider, - HeaderController.provider, ]), ), ) diff --git a/lib/models/account_data.dart b/lib/models/account_data.dart index a325ffe..53b4ff2 100644 --- a/lib/models/account_data.dart +++ b/lib/models/account_data.dart @@ -1,16 +1,71 @@ +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 -abstract class AccountData with _$AccountData { - const factory AccountData({ - required String userId, - required String? roomId, - required String type, - required dynamic content, - }) = _AccountData; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const AccountData({ + @JsonKey(name: AccountData.invitePermissionConfigKey) + final InvitePermissionConfig invitePermissionConfig = + const InvitePermissionConfig(), + + @JsonKey(name: AccountData.directKey) + final IMap> directMessages = const IMap.empty(), + + @JsonKey( + name: AccountData.recentEmojiKey, + readValue: AccountData.readRecentEmojiValue, + toJson: AccountData.recentEmojiToJson, + ) + final IList recentEmoji = const IList.empty(), +}) with _$AccountData { + static List? readRecentEmojiValue( + Map json, + String key, + ) => json[key]?["recent_emoji"]; + + static Map>? recentEmojiToJson( + IList recentEmoji, + ) => {"recent_emoji": recentEmoji.map((emoji) => emoji.toJson()).toList()}; + + static const invitePermissionConfigKey = "m.invite_permission_config"; + static const directKey = "m.direct"; + static const recentEmojiKey = "m.recent_emoji"; + + Map toJson() => _$AccountDataToJson(this); factory AccountData.fromJson(Map json) => _$AccountDataFromJson(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); + + 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); + + factory RecentEmoji.fromJson(Map json) => + _$RecentEmojiFromJson(json); +} + +@JsonEnum(fieldRename: .snake) +enum DefaultInviteAction { + allow, + deny, + @JsonValue("uk.timedout.msc4494.deny_public") + denyPublic, +} diff --git a/lib/models/client_state.dart b/lib/models/client_state.dart index 1e15136..ff433ab 100644 --- a/lib/models/client_state.dart +++ b/lib/models/client_state.dart @@ -1,16 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "client_state.freezed.dart"; part "client_state.g.dart"; -@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; +@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? homeserverUrl, +}) with _$ClientState { + Map toJson() => _$ClientStateToJson(this); 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 29fc471..6b4aec4 100644 --- a/lib/models/configs/members_by_status.dart +++ b/lib/models/configs/members_by_status.dart @@ -1,14 +1,16 @@ 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 -abstract class MembersByStatusConfig with _$MembersByStatusConfig { - const factory MembersByStatusConfig({ - required String roomId, - required MembershipStatus status, - }) = _MembersByStatusConfig; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const MembersByStatusConfig({ + required final String roomId, + required final MembershipStatus status, +}) with _$MembersByStatusConfig { + Map toJson() => _$MembersByStatusConfigToJson(this); factory MembersByStatusConfig.fromJson(Map json) => _$MembersByStatusConfigFromJson(json); diff --git a/lib/models/configs/power_level.dart b/lib/models/configs/power_level.dart index ed9c5e8..5722db7 100644 --- a/lib/models/configs/power_level.dart +++ b/lib/models/configs/power_level.dart @@ -1,6 +1,7 @@ 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 787b28c..b9de793 100644 --- a/lib/models/configs/reactions.dart +++ b/lib/models/configs/reactions.dart @@ -1,13 +1,15 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "reactions.freezed.dart"; part "reactions.g.dart"; -@freezed -abstract class ReactionsConfig with _$ReactionsConfig { - const factory ReactionsConfig({ - required String roomId, - required int eventRowId, - }) = _ReactionsConfig; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const ReactionsConfig({ + required final String roomId, + required final int eventRowId, +}) with _$ReactionsConfig { + Map toJson() => _$ReactionsConfigToJson(this); factory ReactionsConfig.fromJson(Map json) => _$ReactionsConfigFromJson(json); diff --git a/lib/models/configs/user.dart b/lib/models/configs/user.dart index 0331597..bbbc83d 100644 --- a/lib/models/configs/user.dart +++ b/lib/models/configs/user.dart @@ -1,11 +1,13 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "user.freezed.dart"; part "user.g.dart"; -@freezed -abstract class UserConfig with _$UserConfig { - const factory UserConfig({required String? roomId, required String userId}) = - _UserConfig; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const UserConfig({final String? roomId, required final String userId}) + with _$UserConfig { + Map toJson() => _$UserConfigToJson(this); factory UserConfig.fromJson(Map json) => _$UserConfigFromJson(json); diff --git a/lib/models/content/avatar.dart b/lib/models/content/avatar.dart index 66d4c47..25167e9 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 -abstract class AvatarContent extends Content with _$AvatarContent { +sealed 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 636be13..1a35d92 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 -abstract class CanonicalAliasContent extends Content +sealed class CanonicalAliasContent extends Content with _$CanonicalAliasContent { CanonicalAliasContent._(); factory CanonicalAliasContent({ diff --git a/lib/models/content/content.dart b/lib/models/content/content.dart index e7b1141..e28ccbf 100644 --- a/lib/models/content/content.dart +++ b/lib/models/content/content.dart @@ -19,9 +19,11 @@ import "package:nexus/models/content/history_visibility.dart"; class Content { final Error? parseError; - Content({this.parseError}); - factory Content.fromJson(Map json) => Content(); + const Content({this.parseError}); + + factory Content.fromJson(Map json) => const Content(); + Map toJson() => {}; static Map readValue(Map json, _) => @@ -34,13 +36,16 @@ class Content { ?.contentFromJson ?? Content.fromJson)(json); } catch (error) { - if (error is Error) return .new(parseError: error); + if (error is Error) return Content(parseError: error); rethrow; } } } -enum EventType { +enum EventType( + final String type, + final Content Function(Map json) contentFromJson, +) { encrypted("m.room.encrypted", EncryptedContent.fromJson), redaction("m.room.redaction", RedactionContent.fromJson), encryption("m.room.encryption", EncryptionContent.fromJson), @@ -61,8 +66,4 @@ 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 c534558..b937a94 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 -abstract class CreateContent extends Content with _$CreateContent { +sealed class CreateContent extends Content with _$CreateContent { CreateContent._(); factory CreateContent({ @JsonKey(name: "additional_creators") @@ -30,9 +30,10 @@ enum RoomType { space, } -@freezed -abstract class PreviousRoom with _$PreviousRoom { - const factory PreviousRoom({required String roomId}) = _PreviousRoom; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const PreviousRoom({required final String roomId}) with _$PreviousRoom { + Map toJson() => _$PreviousRoomToJson(this); factory PreviousRoom.fromJson(Map json) => _$PreviousRoomFromJson(json); diff --git a/lib/models/content/encrypted.dart b/lib/models/content/encrypted.dart index b33a440..8658a71 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 -abstract class EncryptedContent extends Content with _$EncryptedContent { +sealed 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 3380632..52c7b34 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 -abstract class EncryptionContent extends Content with _$EncryptionContent { +sealed 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 707805c..3cfc011 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 -abstract class HistoryVisibilityContent extends Content +sealed 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 1d14eee..3f1c17b 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 -abstract class JoinRulesContent extends Content with _$JoinRulesContent { +sealed class JoinRulesContent extends Content with _$JoinRulesContent { JoinRulesContent._(); factory JoinRulesContent({ required JoinRule joinRule, @@ -17,12 +17,13 @@ abstract class JoinRulesContent extends Content with _$JoinRulesContent { _$JoinRulesContentFromJson(json); } -@freezed -abstract class AllowCondition with _$AllowCondition { - const factory AllowCondition({ - String? roomId, - required AllowConditionType type, - }) = _AllowCondition; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const AllowCondition({ + final String? roomId, + required final AllowConditionType type, +}) with _$AllowCondition { + Map toJson() => _$AllowConditionToJson(this); factory AllowCondition.fromJson(Map json) => _$AllowConditionFromJson(json); diff --git a/lib/models/content/membership.dart b/lib/models/content/membership.dart index dbbd123..46be974 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 -abstract class MembershipContent extends Content with _$MembershipContent { +sealed 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 e7e8923..36bd725 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") -abstract class MessageContent extends Content with _$MessageContent { +sealed 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 205f6bb..c451d64 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 -abstract class NameContent extends Content with _$NameContent { +sealed 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 8aea838..9dac5de 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 -abstract class PinnedEventsContent extends Content with _$PinnedEventsContent { +sealed 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 3709c38..5ae6561 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 -abstract class PowerLevelsContent extends Content with _$PowerLevelsContent { +sealed class PowerLevelsContent extends Content with _$PowerLevelsContent { PowerLevelsContent._(); factory PowerLevelsContent({ @Default(IMap.empty()) IMap events, @@ -24,12 +24,13 @@ abstract class PowerLevelsContent extends Content with _$PowerLevelsContent { _$PowerLevelsContentFromJson(json); } -@freezed -abstract class Notifications with _$Notifications { - const factory Notifications({ - @Default(50) int room, - @Default(IMapConst({})) IMap other, - }) = _Notifications; +@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); factory Notifications.fromJson(Map json) => _$NotificationsFromJson(json); diff --git a/lib/models/content/reaction.dart b/lib/models/content/reaction.dart index 0f81bc0..93c563d 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) -abstract class ReactionContent extends Content with _$ReactionContent { +sealed 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 e9c1a90..2ed6436 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 -abstract class RedactionContent extends Content with _$RedactionContent { +sealed 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 1e50988..3873bf3 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 -abstract class ServerACLContent extends Content with _$ServerACLContent { +sealed 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 89d9332..c6fa893 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 -abstract class StickerContent extends Content with _$StickerContent { +sealed 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 8fa5229..c8256e3 100644 --- a/lib/models/content/topic.dart +++ b/lib/models/content/topic.dart @@ -1,11 +1,12 @@ 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 -abstract class TopicContent extends Content with _$TopicContent { +sealed class TopicContent extends Content with _$TopicContent { TopicContent._(); factory TopicContent({ required String topic, @@ -16,24 +17,24 @@ abstract class TopicContent extends Content with _$TopicContent { _$TopicContentFromJson(json); } -@freezed -abstract class TopicContentBlock with _$TopicContentBlock { - factory TopicContentBlock({ - @Default(IList.empty()) - @JsonKey(name: "m.text") - IList representations, - }) = _TopicContentBlock; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const TopicContentBlock({ + final IList representations = const IList.empty(), +}) with _$TopicContentBlock { + Map toJson() => _$TopicContentBlockToJson(this); factory TopicContentBlock.fromJson(Map json) => _$TopicContentBlockFromJson(json); } -@freezed -abstract class TextualRepresentation with _$TextualRepresentation { - factory TextualRepresentation({ - required String body, - @Default("text/plain") String mimetype, - }) = _TextualRepresentation; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const TextualRepresentation({ + required final String body, + final String mimetype = "text/plain", +}) with _$TextualRepresentation { + Map toJson() => _$TextualRepresentationToJson(this); factory TextualRepresentation.fromJson(Map json) => _$TextualRepresentationFromJson(json); diff --git a/lib/models/emoji.dart b/lib/models/emoji.dart index 8e4eac6..149ae18 100644 --- a/lib/models/emoji.dart +++ b/lib/models/emoji.dart @@ -3,15 +3,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; part "emoji.freezed.dart"; part "emoji.g.dart"; -@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; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class Emoji({ + required final String emoji, + required final String category, + required final IList aliases, + required final String description, + required final IList tags, +}) with _$Emoji { + Map toJson() => _$EmojiToJson(this); factory Emoji.fromJson(Map json) => _$EmojiFromJson(json); } diff --git a/lib/models/event.dart b/lib/models/event.dart index c54dbc5..d06b41a 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -2,12 +2,40 @@ 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/epoch_date_time_converter.dart"; -import "package:nexus/models/profile.dart"; +import "package:nexus/models/profile_response.dart"; + part "event.freezed.dart"; part "event.g.dart"; -@freezed -abstract class Event with _$Event { +@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() @override 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") @override final int lastEditRowId = 0, + @UnreadTypeConverter() @override final UnreadType? unreadType, + final Profile? pmp, + required final Content content, + required final Content? previousContent, +}) with _$Event { + Map toJson() => _$EventToJson(this); + static String typeJsonFromJson(Map json, _) => json["decrypted_type"] ?? json["type"]; @@ -17,37 +45,17 @@ abstract class Event with _$Event { return content["m.new_content"] ?? content; } - 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; + static String? replyToFromJson(Map json) { + try { + return json["m.relates_to"]?["m.in_reply_to"]?["event_id"]; + } catch (_) { + return null; + } + } factory Event.fromJson(Map json) => _$EventFromJson(json).copyWith( - replyTo: getContentFromJson( - json, - )["m.relates_to"]?["m.in_reply_to"]?["event_id"], + replyTo: replyToFromJson(getContentFromJson(json)), pmp: json["content"]?["com.beeper.per_message_profile"] == null ? null : Profile.fromJsonWithCatch( @@ -66,17 +74,19 @@ abstract class Event with _$Event { ); } -@freezed -abstract class LocalContent with _$LocalContent { - const factory LocalContent({ - String? sanitizedHtml, - String? editSource, - bool? wasPlaintext, - bool? bigEmoji, - bool? hasMath, - bool? replyFallbackRemoved, - }) = _LocalContent; +@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); + @override factory LocalContent.fromJson(Map json) => _$LocalContentFromJson(json); } diff --git a/lib/models/homeserver.dart b/lib/models/homeserver.dart index 903e23d..098dd12 100644 --- a/lib/models/homeserver.dart +++ b/lib/models/homeserver.dart @@ -1,12 +1,11 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "homeserver.freezed.dart"; @freezed -abstract class Homeserver with _$Homeserver { - const factory Homeserver({ - required String name, - required String description, - required Uri url, - required String iconUrl, - }) = _Homeserver; -} +class const Homeserver({ + @override required final String name, + @override required final String description, + @override required final Uri url, + @override required final String iconUrl, +}) with _$Homeserver; diff --git a/lib/models/info/audio.dart b/lib/models/info/audio.dart index ccfcf7a..ac90282 100644 --- a/lib/models/info/audio.dart +++ b/lib/models/info/audio.dart @@ -1,16 +1,17 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/ms_duration.dart"; + part "audio.freezed.dart"; part "audio.g.dart"; -@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; +@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); factory AudioInfo.fromJson(Map json) => _$AudioInfoFromJson(json); diff --git a/lib/models/info/file.dart b/lib/models/info/file.dart index 1509c99..ce0aa84 100644 --- a/lib/models/info/file.dart +++ b/lib/models/info/file.dart @@ -1,14 +1,15 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "file.freezed.dart"; part "file.g.dart"; -@freezed -abstract class FileInfo with _$FileInfo { - /// Information for images, [size] is in bytes. - const factory FileInfo({ - @JsonKey(name: "mimetype") String? mimeType, - int? size, - }) = _FileInfo; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const FileInfo({ + @JsonKey(name: "mimetype") final String? mimeType, + final int? size, +}) with _$FileInfo { + Map toJson() => _$FileInfoToJson(this); factory FileInfo.fromJson(Map json) => _$FileInfoFromJson(json); diff --git a/lib/models/info/image.dart b/lib/models/info/image.dart index 9833016..266fb01 100644 --- a/lib/models/info/image.dart +++ b/lib/models/info/image.dart @@ -1,17 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "image.freezed.dart"; part "image.g.dart"; -@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; +@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); factory ImageInfo.fromJson(Map json) => _$ImageInfoFromJson(json); diff --git a/lib/models/info/video.dart b/lib/models/info/video.dart index 6ff3547..31b1fb6 100644 --- a/lib/models/info/video.dart +++ b/lib/models/info/video.dart @@ -1,18 +1,19 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/ms_duration.dart"; + part "video.freezed.dart"; part "video.g.dart"; -@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; +@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); factory VideoInfo.fromJson(Map json) => _$VideoInfoFromJson(json); diff --git a/lib/models/lazy_load_summary.dart b/lib/models/lazy_load_summary.dart index 0cd250f..f9a814e 100644 --- a/lib/models/lazy_load_summary.dart +++ b/lib/models/lazy_load_summary.dart @@ -1,15 +1,17 @@ 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 -abstract class LazyLoadSummary with _$LazyLoadSummary { - const factory LazyLoadSummary({ - required IList? heroes, - required int? joinedMemberCount, - required int? invitedMemberCount, - }) = _LazyLoadSummary; +@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); factory LazyLoadSummary.fromJson(Map json) => _$LazyLoadSummaryFromJson(json); diff --git a/lib/models/oauth_auth_code_response.dart b/lib/models/oauth_auth_code_response.dart index 5bb3f3f..3074335 100644 --- a/lib/models/oauth_auth_code_response.dart +++ b/lib/models/oauth_auth_code_response.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "oauth_auth_code_response.freezed.dart"; part "oauth_auth_code_response.g.dart"; -@freezed -abstract class OAuthAuthCodeResponse with _$OAuthAuthCodeResponse { - const factory OAuthAuthCodeResponse({ - required String state, - required String codeVerifier, - required Uri url, - }) = _OAuthAuthCodeResponse; +@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); factory OAuthAuthCodeResponse.fromJson(Map json) => _$OAuthAuthCodeResponseFromJson(json); diff --git a/lib/models/open_graph_data.dart b/lib/models/open_graph_data.dart index d7e840d..a183ef7 100644 --- a/lib/models/open_graph_data.dart +++ b/lib/models/open_graph_data.dart @@ -1,16 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "open_graph_data.freezed.dart"; part "open_graph_data.g.dart"; -@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; +@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); factory OpenGraphData.fromJson(Map json) => _$OpenGraphDataFromJson(json); diff --git a/lib/models/paginate.dart b/lib/models/paginate.dart index df0a0f6..dc6eede 100644 --- a/lib/models/paginate.dart +++ b/lib/models/paginate.dart @@ -1,16 +1,18 @@ 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 -abstract class Paginate with _$Paginate { - const factory Paginate({ - required IList events, - required IList relatedEvents, - required bool hasMore, - }) = _Paginate; +@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); factory Paginate.fromJson(Map json) => _$PaginateFromJson(json); diff --git a/lib/models/profile.dart b/lib/models/profile.dart deleted file mode 100644 index 6ae1e94..0000000 --- a/lib/models/profile.dart +++ /dev/null @@ -1,52 +0,0 @@ -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.freezed.dart"; -part "profile.g.dart"; - -@freezed -abstract class Profile with _$Profile { - static Object? readPronouns(Map map, _) => - map["m.pronouns"] ?? map["io.fsky.nyx.pronouns"]; - - static Object? readTimezone(Map map, _) => - map["m.tz"] ?? map["us.cloke.msc4175.tz"]; - - const factory Profile({ - required 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: "io.fsky.nyx.pronouns") - IList pronouns, - }) = _Profile; - - factory Profile.fromJson(Map json) => - _$ProfileFromJson(json); - - factory Profile.fromJsonWithCatch(Map json) { - try { - return Profile.fromJson(json); - } catch (error) { - return Profile(id: json["id"], parseError: error.toString()); - } - } -} - -@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/profile_response.dart b/lib/models/profile_response.dart new file mode 100644 index 0000000..8984bcf --- /dev/null +++ b/lib/models/profile_response.dart @@ -0,0 +1,75 @@ +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, + required final Bio? bio, +}) with _$ProfileResponse { + Map toJson() => _$ProfileResponseToJson(this); + + 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); + + 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); + + 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"]; + + factory Profile.fromJson(Map json) => + _$ProfileFromJson(json); + + factory Profile.fromJsonWithCatch(Map json) { + try { + return Profile.fromJson(json); + } catch (error) { + return _Profile(parseError: error.toString()); + } + } +} + +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const Pronoun({ + required final String language, + required final String summary, +}) with _$Pronoun { + Map toJson() => _$PronounToJson(this); + + factory Pronoun.fromJson(Map json) => + _$PronounFromJson(json); +} diff --git a/lib/models/read_receipt.dart b/lib/models/read_receipt.dart index d533e2d..ab4edd8 100644 --- a/lib/models/read_receipt.dart +++ b/lib/models/read_receipt.dart @@ -1,17 +1,19 @@ 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 -abstract class ReadReceipt with _$ReadReceipt { - const factory ReadReceipt({ - String? roomId, - required String userId, - String? threadId, - required String eventId, - @EpochDateTimeConverter() required DateTime timestamp, - }) = _ReadReceipt; +@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); factory ReadReceipt.fromJson(Map json) => _$ReadReceiptFromJson(json); diff --git a/lib/models/requests/download_media.dart b/lib/models/requests/download_media.dart index b5d5771..c9751f8 100644 --- a/lib/models/requests/download_media.dart +++ b/lib/models/requests/download_media.dart @@ -1,15 +1,17 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "download_media.freezed.dart"; part "download_media.g.dart"; -@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; +@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); factory DownloadMediaRequest.fromJson(Map json) => _$DownloadMediaRequestFromJson(json); diff --git a/lib/models/requests/get_event.dart b/lib/models/requests/get_event.dart index 2665a2a..b4692ee 100644 --- a/lib/models/requests/get_event.dart +++ b/lib/models/requests/get_event.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "get_event.freezed.dart"; part "get_event.g.dart"; -@freezed -abstract class GetEventRequest with _$GetEventRequest { - const factory GetEventRequest({ - required String roomId, - required String eventId, - @Default(false) bool unredact, - }) = _GetEventRequest; +@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); factory GetEventRequest.fromJson(Map json) => _$GetEventRequestFromJson(json); diff --git a/lib/models/requests/get_related_events.dart b/lib/models/requests/get_related_events.dart index 52d2716..08be426 100644 --- a/lib/models/requests/get_related_events.dart +++ b/lib/models/requests/get_related_events.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "get_related_events.freezed.dart"; part "get_related_events.g.dart"; -@freezed -abstract class GetRelatedEventsRequest with _$GetRelatedEventsRequest { - const factory GetRelatedEventsRequest({ - required String roomId, - required String eventId, - required String relationType, - }) = _GetRelatedEventsRequest; +@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); 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 d3f52f7..66504da 100644 --- a/lib/models/requests/get_room_state.dart +++ b/lib/models/requests/get_room_state.dart @@ -1,15 +1,17 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "get_room_state.freezed.dart"; part "get_room_state.g.dart"; -@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; +@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); factory GetRoomStateRequest.fromJson(Map json) => _$GetRoomStateRequestFromJson(json); diff --git a/lib/models/requests/join_room.dart b/lib/models/requests/join_room.dart index 8487298..350b762 100644 --- a/lib/models/requests/join_room.dart +++ b/lib/models/requests/join_room.dart @@ -1,14 +1,16 @@ 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 -abstract class JoinRoomRequest with _$JoinRoomRequest { - const factory JoinRoomRequest({ - required String roomIdOrAlias, - required IList via, - }) = _JoinRoomRequest; +@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); 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 9d55c1f..fb47358 100644 --- a/lib/models/requests/oauth/exchange_token.dart +++ b/lib/models/requests/oauth/exchange_token.dart @@ -1,16 +1,18 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "exchange_token.freezed.dart"; part "exchange_token.g.dart"; -@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; +@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); 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 5ca5b6f..1ff166a 100644 --- a/lib/models/requests/oauth/get_auth_url.dart +++ b/lib/models/requests/oauth/get_auth_url.dart @@ -2,25 +2,27 @@ 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 -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; +@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); factory OAuthGetAuthUrl.fromJson(Map json) => _$OAuthGetAuthUrlFromJson(json); } -abstract class Scope { +sealed 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 cf4f9e0..e5c1a70 100644 --- a/lib/models/requests/oauth/register_client.dart +++ b/lib/models/requests/oauth/register_client.dart @@ -1,26 +1,27 @@ 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 -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, +@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, - @Default(AuthMethod.none) - @JsonKey(name: "token_endpoint_auth_method") - AuthMethod? authMethod, - }) = _OAuthRegisterClientRequest; + @JsonKey(name: "token_endpoint_auth_method") + final AuthMethod? authMethod = AuthMethod.none, +}) with _$OAuthRegisterClientRequest { + Map toJson() => _$OAuthRegisterClientRequestToJson(this); factory OAuthRegisterClientRequest.fromJson(Map json) => _$OAuthRegisterClientRequestFromJson(json); diff --git a/lib/models/requests/paginate.dart b/lib/models/requests/paginate.dart index ddc62f3..b3903c6 100644 --- a/lib/models/requests/paginate.dart +++ b/lib/models/requests/paginate.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "paginate.freezed.dart"; part "paginate.g.dart"; -@freezed -abstract class PaginateRequest with _$PaginateRequest { - const factory PaginateRequest({ - required String roomId, - required int? maxTimelineId, - @Default(20) int limit, - }) = _PaginateRequest; +@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); factory PaginateRequest.fromJson(Map json) => _$PaginateRequestFromJson(json); diff --git a/lib/models/requests/report.dart b/lib/models/requests/report.dart index f87b1f1..ed05715 100644 --- a/lib/models/requests/report.dart +++ b/lib/models/requests/report.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "report.freezed.dart"; part "report.g.dart"; -@freezed -abstract class ReportRequest with _$ReportRequest { - const factory ReportRequest({ - required String roomId, - required String eventId, - String? reason, - }) = _ReportRequest; +@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); factory ReportRequest.fromJson(Map json) => _$ReportRequestFromJson(json); diff --git a/lib/models/requests/send_event.dart b/lib/models/requests/send_event.dart index 196c0b5..acad57f 100644 --- a/lib/models/requests/send_event.dart +++ b/lib/models/requests/send_event.dart @@ -1,19 +1,21 @@ 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 -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; +@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); factory SendEventRequest.fromJson(Map json) => _$SendEventRequestFromJson(json); diff --git a/lib/models/requests/send_message.dart b/lib/models/requests/send_message.dart index 951198e..e9bbbb2 100644 --- a/lib/models/requests/send_message.dart +++ b/lib/models/requests/send_message.dart @@ -2,43 +2,42 @@ 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 -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; +@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); factory SendMessageRequest.fromJson(Map json) => _$SendMessageRequestFromJson(json); } -@freezed -abstract class Mentions with _$Mentions { - const factory Mentions({ - @Default(false) bool room, - @Default(IList.empty()) IList userIds, - }) = _Mentions; +@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); factory Mentions.fromJson(Map json) => _$MentionsFromJson(json); } @Freezed(toJson: false) -abstract class Relation with _$Relation { - const Relation._(); - - const factory Relation({ - required String eventId, - required RelationType relationType, - }) = _Relation; - +class const Relation({ + required final String eventId, + required final RelationType relationType, +}) with _$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 new file mode 100644 index 0000000..95ef75c --- /dev/null +++ b/lib/models/requests/set_account_data.dart @@ -0,0 +1,17 @@ +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); + + factory SetAccountDataRequest.fromJson(Map json) => + _$SetAccountDataRequestFromJson(json); +} diff --git a/lib/models/requests/set_membership.dart b/lib/models/requests/set_membership.dart index 4bbe8a3..9d88da2 100644 --- a/lib/models/requests/set_membership.dart +++ b/lib/models/requests/set_membership.dart @@ -1,18 +1,19 @@ 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 -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; +@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); factory SetMembershipRequest.fromJson(Map json) => _$SetMembershipRequestFromJson(json); diff --git a/lib/models/requests/set_state.dart b/lib/models/requests/set_state.dart index c92763d..d0e3e95 100644 --- a/lib/models/requests/set_state.dart +++ b/lib/models/requests/set_state.dart @@ -1,22 +1,23 @@ 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 -abstract class SetStateRequest with _$SetStateRequest { - const factory SetStateRequest({ - required String roomId, - required String type, - required String stateKey, - required Content content, +@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, - @JsonKey(name: "delay_ms", includeIfNull: false) - @MSDuration() - @Default(null) - Duration? delay, - }) = _SetStateRequest; + @JsonKey(name: "delay_ms", includeIfNull: false) + @MSDuration() + final Duration? delay, +}) with _$SetStateRequest { + Map toJson() => _$SetStateRequestToJson(this); factory SetStateRequest.fromJson(Map json) => _$SetStateRequestFromJson(json); diff --git a/lib/models/requests/upload_media.dart b/lib/models/requests/upload_media.dart index a640bea..d6e69ac 100644 --- a/lib/models/requests/upload_media.dart +++ b/lib/models/requests/upload_media.dart @@ -1,23 +1,26 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "upload_media.freezed.dart"; part "upload_media.g.dart"; -@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; +@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); factory UploadMediaRequest.fromJson(Map json) => _$UploadMediaRequestFromJson(json); diff --git a/lib/models/room.dart b/lib/models/room.dart index fb21a55..d030043 100644 --- a/lib/models/room.dart +++ b/lib/models/room.dart @@ -3,11 +3,38 @@ 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 -abstract class Room with _$Room { +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const Room({ + @JsonKey(name: "meta") final RoomMetadata? metadata, + + @JsonKey(fromJson: Room.timelineTupleJsonToIMap) + final IMap timeline = const IMap.empty(), + + final ISet sticky = const ISet.empty(), + + @JsonKey(fromJson: Room.eventsJsonToIMap) + final IMap events = const IMap.empty(), + + final bool reset = false, + final bool hasFetchedState = 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 { + /// [timeline] is an IMap of timelineRowId to eventRowId + /// [events] is an IMap of eventRowId to event + /// [sticky] is an ISet of eventRowId static IMap timelineTupleJsonToIMap(List json) => IMap.fromEntries( json.map( @@ -26,32 +53,7 @@ abstract class Room with _$Room { }), ); - /// [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; + Map toJson() => _$RoomToJson(this); factory Room.fromJson(Map json) => _$RoomFromJson(json); } diff --git a/lib/models/room_metadata.dart b/lib/models/room_metadata.dart index 7c16cae..3aafd0e 100644 --- a/lib/models/room_metadata.dart +++ b/lib/models/room_metadata.dart @@ -1,29 +1,31 @@ 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 -abstract class RoomMetadata with _$RoomMetadata { - const factory RoomMetadata({ - @JsonKey(name: "room_id") required String id, +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const RoomMetadata({ + @JsonKey(name: "room_id") required final String id, - // 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; + // 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); factory RoomMetadata.fromJson(Map json) => _$RoomMetadataFromJson(json); diff --git a/lib/models/room_summary.dart b/lib/models/room_summary.dart new file mode 100644 index 0000000..7c2d6d2 --- /dev/null +++ b/lib/models/room_summary.dart @@ -0,0 +1,25 @@ +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 6a49845..2ff541d 100644 --- a/lib/models/setting.dart +++ b/lib/models/setting.dart @@ -1,16 +1,13 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; -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, - }); -} +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, +}); diff --git a/lib/models/settings.dart b/lib/models/settings.dart index eddc2da..7f73375 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -1,15 +1,17 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:freezed_annotation/freezed_annotation.dart"; + part "settings.freezed.dart"; part "settings.g.dart"; -@freezed -abstract class Settings with _$Settings { - const factory Settings({ - @Default(ThemeMode.system) ThemeMode theme, - @Default(true) bool useDynamicTheming, - @Default(false) bool linuxMobileMode, - }) = _Settings; +@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); factory Settings.fromJson(Map json) => _$SettingsFromJson(json); diff --git a/lib/models/settings_category.dart b/lib/models/settings_category.dart index 88bfdd0..2dc7076 100644 --- a/lib/models/settings_category.dart +++ b/lib/models/settings_category.dart @@ -1,14 +1,13 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:freezed_annotation/freezed_annotation.dart"; import "package:nexus/models/setting.dart"; + part "settings_category.freezed.dart"; @freezed -abstract class SettingsCategory with _$SettingsCategory { - const factory SettingsCategory({ - required String title, - required IconData icon, - required IList settings, - }) = _SettingsCategory; -} +class const SettingsCategory({ + required final String title, + required final IconData icon, + required final IList settings, +}) with _$SettingsCategory; diff --git a/lib/models/space.dart b/lib/models/space.dart index 73fbbc6..97efe7a 100644 --- a/lib/models/space.dart +++ b/lib/models/space.dart @@ -3,16 +3,15 @@ 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 -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; -} +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; diff --git a/lib/models/space_edge.dart b/lib/models/space_edge.dart index 192af31..ea74130 100644 --- a/lib/models/space_edge.dart +++ b/lib/models/space_edge.dart @@ -1,13 +1,15 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "space_edge.freezed.dart"; part "space_edge.g.dart"; -@freezed -abstract class SpaceEdge with _$SpaceEdge { - const factory SpaceEdge({ - required String childId, - @Default(false) bool suggested, - }) = _SpaceEdge; +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const SpaceEdge({ + required final String childId, + final bool suggested = false, +}) with _$SpaceEdge { + Map toJson() => _$SpaceEdgeToJson(this); factory SpaceEdge.fromJson(Map json) => _$SpaceEdgeFromJson(json); diff --git a/lib/models/spec_versions_response.dart b/lib/models/spec_versions_response.dart new file mode 100644 index 0000000..1f414d2 --- /dev/null +++ b/lib/models/spec_versions_response.dart @@ -0,0 +1,28 @@ +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); + + 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); + + factory UnstableFeatures.fromJson(Map json) => + _$UnstableFeaturesFromJson(json); +} diff --git a/lib/models/subspace.dart b/lib/models/subspace.dart index 1a1879c..97d1de2 100644 --- a/lib/models/subspace.dart +++ b/lib/models/subspace.dart @@ -1,10 +1,11 @@ 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 -abstract class Subspace with _$Subspace { - const factory Subspace({required Room room, required IList children}) = - _Subspace; -} +class const Subspace({ + required final Room room, + required final IList children, +}) with _$Subspace; diff --git a/lib/models/sync_data.dart b/lib/models/sync_data.dart index 0f98bb2..a9c10bf 100644 --- a/lib/models/sync_data.dart +++ b/lib/models/sync_data.dart @@ -1,22 +1,22 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/account_data.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 -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; +@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); factory SyncData.fromJson(Map json) => _$SyncDataFromJson(json); diff --git a/lib/models/sync_status.dart b/lib/models/sync_status.dart index 7848fbe..57bd882 100644 --- a/lib/models/sync_status.dart +++ b/lib/models/sync_status.dart @@ -1,14 +1,16 @@ import "package:freezed_annotation/freezed_annotation.dart"; + part "sync_status.freezed.dart"; part "sync_status.g.dart"; -@freezed -abstract class SyncStatus with _$SyncStatus { - const factory SyncStatus({ - required SyncStatusType type, - String? error, - required int errorCount, - }) = _SyncStatus; +@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); factory SyncStatus.fromJson(Map json) => _$SyncStatusFromJson(json); diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index 316c9af..c58b7a7 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/emoji.dart"; import "package:nexus/controllers/init_complete.dart"; @@ -8,9 +8,7 @@ import "package:nexus/widgets/sidebar.dart"; import "package:nexus/widgets/room_chat.dart"; import "package:nexus/widgets/loading.dart"; -class ChatPage extends ConsumerWidget { - const ChatPage({super.key}); - +class const ChatPage({super.key}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (context, constraints) { diff --git a/lib/pages/select_server.dart b/lib/pages/select_server.dart index f7cefb8..85c84cb 100644 --- a/lib/pages/select_server.dart +++ b/lib/pages/select_server.dart @@ -1,5 +1,5 @@ import "package:app_links/app_links.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; @@ -13,9 +13,7 @@ import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; -class SelectServerPage extends HookConsumerWidget { - const SelectServerPage({super.key}); - +class const SelectServerPage({super.key}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -26,6 +24,17 @@ class SelectServerPage 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 { @@ -33,26 +42,14 @@ class SelectServerPage extends HookConsumerWidget { newHomeserver = Uri.https(newHomeserver!.path); } - final newUrl = newHomeserver == null - ? null - : await ref - .read(ClientController.provider.notifier) - .discoverHomeserver(newHomeserver); + final newUrl = await ref + .read(ClientController.provider.notifier) + .discoverHomeserver(newHomeserver!); if (context.mounted) { - 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 { + Future tryLogin(Uri url) async { final codeResponse = await ref.watch( - AuthUrlController.provider(newUrl).future, + AuthUrlController.provider(url).future, ); await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url); @@ -60,30 +57,54 @@ class SelectServerPage extends HookConsumerWidget { AppLinks().uriLinkStream.listen((encodedUri) async { final state = encodedUri.queryParameters["state"]; if (state != codeResponse.state) return; + isLoading.value = true; - final code = encodedUri.queryParameters["code"]; + try { + final code = encodedUri.queryParameters["code"]; - if (code != null) { - await ref - .watch(ClientController.provider.notifier) - .exchangeToken( - .new( - homeserverUrl: newUrl, - codeVerifier: codeResponse.codeVerifier, - redirectUri: .new( - scheme: "nexus.federated.nexus", - path: "/", + 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, + ), ), - code: code, - clientId: await ref.watch( - ClientIdController.provider(newUrl).future, - ), - ), - ) - .onError(showError); + ) + .onError(showError); + } + } finally { + isLoading.value = false; } }); } + + if (newUrl == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Homeserver verification failed.", + style: .new(color: theme.colorScheme.onErrorContainer), + ), + action: SnackBarAction( + onPressed: () => tryLogin(newHomeserver!), + label: "Attempt log in anyways", + textColor: theme.colorScheme.onErrorContainer, + ), + backgroundColor: theme.colorScheme.errorContainer, + ), + ); + } else { + tryLogin(newUrl); + } } } catch (error, stackTrace) { showError(error, stackTrace); @@ -111,7 +132,7 @@ class SelectServerPage extends HookConsumerWidget { children: [ Row( children: [ - SvgPicture.asset("assets/icon.svg", width: 128), + SvgPicture.asset("assets/bundled/icon.svg", width: 128), SizedBox(width: 12), Expanded( child: Column( @@ -158,23 +179,20 @@ class SelectServerPage 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/pages/settings.dart b/lib/pages/settings.dart index fa4d8da..ed9dc59 100644 --- a/lib/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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_card_list/m3e_card_list.dart"; @@ -13,9 +13,7 @@ import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/highlight_wrapper.dart"; import "package:super_sliver_list/super_sliver_list.dart"; -class SettingsPage extends ConsumerWidget { - const SettingsPage({super.key}); - +class const SettingsPage({super.key}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (_, constraints) => HookBuilder( @@ -142,12 +140,12 @@ class SettingsPage 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( @@ -172,30 +170,34 @@ class SettingsPage extends ConsumerWidget { ) : Row( children: [ - 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, + 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, + ), ), VerticalDivider(), Expanded( diff --git a/lib/pages/settings_category.dart b/lib/pages/settings_category.dart index b21d048..8e94c56 100644 --- a/lib/pages/settings_category.dart +++ b/lib/pages/settings_category.dart @@ -1,7 +1,7 @@ import "dart:async"; import "package:collection/collection.dart"; -import "package:flutter/material.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/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 SettingsCategoryPage extends HookConsumerWidget { - final int index; - final int? initialHighlight; - const SettingsCategoryPage(this.index, {this.initialHighlight, super.key}); - +class const SettingsCategoryPage( + final int index, { + final int? initialHighlight, + super.key, +}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final highlight = useState(initialHighlight); diff --git a/lib/pages/verify.dart b/lib/pages/verify.dart index 4eefa6c..42a8dfc 100644 --- a/lib/pages/verify.dart +++ b/lib/pages/verify.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.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/client.dart"; @@ -6,9 +6,7 @@ import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/helpers/required_validator_helper.dart"; -class VerifyPage extends HookConsumerWidget { - const VerifyPage({super.key}); - +class const VerifyPage({super.key}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final passphraseController = useTextEditingController(); diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index 9da03aa..a6058ea 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -1,28 +1,20 @@ import "dart:io"; + import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/settings.dart"; import "package:window_manager/window_manager.dart"; -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(), - }); - +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 { @override Size get preferredSize => const .fromHeight(kToolbarHeight); diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart index 75c08b9..982d56e 100644 --- a/lib/widgets/avatar_or_hash.dart +++ b/lib/widgets/avatar_or_hash.dart @@ -1,21 +1,15 @@ import "package:color_hash/color_hash.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/helpers/mxc_image.dart"; -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, - }); - +final class const AvatarOrHash( + final Uri? avatar, + final String title, { + final Widget? fallback, + final double height = 24, + super.key, +}) extends ConsumerWidget { @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 c63d939..9a597a4 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -1,7 +1,8 @@ import "dart:io"; + import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:file_selector/file_selector.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:fluttertagger/fluttertagger.dart"; @@ -17,28 +18,20 @@ import "package:nexus/widgets/composer/relation_preview.dart"; import "package:nexus/widgets/emoji_picker_button.dart"; import "package:nexus/main.dart"; -class Composer extends HookConsumerWidget { - final String roomId; - final Event? relatedEvent; - final RelationType relationType; - final VoidCallback onDismiss; - final FocusNode? node; - final Future Function( +class const Composer( + final String roomId, { + required final Event? relatedEvent, + required final RelationType relationType, + required final VoidCallback onDismiss, + required final Future Function( String text, { required bool shouldMention, required IList tags, }) - onSend; - const Composer( - this.roomId, { - required this.relatedEvent, - required this.relationType, - required this.onDismiss, - required this.onSend, - this.node, - super.key, - }); - + onSend, + final FocusNode? node, + super.key, +}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -59,7 +52,10 @@ class Composer extends HookConsumerWidget { final attachment = ref.watch(AttachmentController.provider(roomId)); void send() { - if (controller.value.text.isEmpty && attachment == null) return; + if (controller.value.text.isEmpty && attachment == null || + attachment != null && attachment.$2 == null) { + return; + } onSend( controller.value.formattedText, shouldMention: shouldMention.value, @@ -140,9 +136,8 @@ class Composer extends HookConsumerWidget { ), onTap: () async => ref .watch( - AttachmentController.provider( - roomId, - ).notifier, + AttachmentController.provider(roomId) + .notifier, ) .add( (await ref @@ -160,9 +155,8 @@ class Composer extends HookConsumerWidget { ), onTap: () async => ref .watch( - AttachmentController.provider( - roomId, - ).notifier, + AttachmentController.provider(roomId) + .notifier, ) .add( (await ref @@ -176,9 +170,8 @@ class Composer extends HookConsumerWidget { PopupMenuItem( onTap: () async => ref .watch( - AttachmentController.provider( - roomId, - ).notifier, + AttachmentController.provider(roomId) + .notifier, ) .add((await openFile())!) .onError(showError), @@ -231,7 +224,12 @@ class Composer extends HookConsumerWidget { child: TextField( maxLines: 12, minLines: 1, - autofocus: true, + autofocus: + (Platform.isLinux || + Platform.isMacOS || + Platform.isWindows) + ? true + : false, decoration: .new( hintText: "Your message here...", border: .none, diff --git a/lib/widgets/composer/mention_overlay.dart b/lib/widgets/composer/mention_overlay.dart index ca4f95a..3dc0604 100644 --- a/lib/widgets/composer/mention_overlay.dart +++ b/lib/widgets/composer/mention_overlay.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/members_by_status.dart"; import "package:nexus/controllers/rooms.dart"; @@ -9,19 +9,14 @@ 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, - }); - +class const MentionOverlay( + final String roomId, { + required final String query, + 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) { final rooms = ref.watch(RoomsController.provider); diff --git a/lib/widgets/composer/relation_preview.dart b/lib/widgets/composer/relation_preview.dart index c9cc271..fca9de6 100644 --- a/lib/widgets/composer/relation_preview.dart +++ b/lib/widgets/composer/relation_preview.dart @@ -1,25 +1,17 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 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, - }); - +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 { @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 2b0f9bd..c44ae11 100644 --- a/lib/widgets/divider_text.dart +++ b/lib/widgets/divider_text.dart @@ -1,11 +1,8 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:nexus/widgets/divider_widget.dart"; -class DividerText extends StatelessWidget { - final String text; - - const DividerText(this.text, {super.key}); - +final class const DividerText(final String text, {super.key}) + extends StatelessWidget { @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 6f13bd4..5e4972c 100644 --- a/lib/widgets/divider_widget.dart +++ b/lib/widgets/divider_widget.dart @@ -1,9 +1,7 @@ -import "package:flutter/material.dart"; - -class DividerWidget extends StatelessWidget { - final Widget widget; - const DividerWidget(this.widget, {super.key}); +import "package:material_ui/material_ui.dart"; +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_button.dart b/lib/widgets/emoji_picker_button.dart index 2ac906a..1e20557 100644 --- a/lib/widgets/emoji_picker_button.dart +++ b/lib/widgets/emoji_picker_button.dart @@ -1,21 +1,15 @@ import "package:emoji_text_field/emoji_text_field.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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, - }); - +final class const EmojiPickerButton({ + required final BuildContext context, + final TextEditingController? controller, + final void Function(String emoji)? onSelection, + final VoidCallback? onPressed, + super.key, +}) extends HookConsumerWidget { @override Widget build(_, WidgetRef ref) => IconButton( onPressed: () async { diff --git a/lib/widgets/error_dialog.dart b/lib/widgets/error_dialog.dart index 9b62200..ddc4c98 100644 --- a/lib/widgets/error_dialog.dart +++ b/lib/widgets/error_dialog.dart @@ -1,13 +1,13 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/misc.dart"; -class ErrorDialog extends ConsumerWidget { - final Object error; - final StackTrace? stackTrace; - final ProviderOrFamily? provider; - const ErrorDialog(this.error, this.stackTrace, {this.provider, super.key}); - +final class const ErrorDialog( + final Object error, + final StackTrace? stackTrace, { + final ProviderOrFamily? provider, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return AlertDialog( diff --git a/lib/widgets/event_preview.dart b/lib/widgets/event_preview.dart index 7a40a75..30dfbfe 100644 --- a/lib/widgets/event_preview.dart +++ b/lib/widgets/event_preview.dart @@ -1,14 +1,12 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 EventPreview extends StatelessWidget { - final Event event; - const EventPreview(this.event, {super.key}); - +class const EventPreview(final Event event, {super.key}) + extends StatelessWidget { @override Widget build(BuildContext context) => IgnorePointer( child: Padding( diff --git a/lib/widgets/expandable_image.dart b/lib/widgets/expandable_image.dart index ee4f900..0639417 100644 --- a/lib/widgets/expandable_image.dart +++ b/lib/widgets/expandable_image.dart @@ -1,15 +1,15 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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"; -class ExpandableImage extends ConsumerWidget { - final Widget child; - final DownloadMediaRequest? request; - const ExpandableImage(this.request, {required this.child, super.key}); - +final class const ExpandableImage( + final DownloadMediaRequest? request, { + required final Widget child, + super.key, +}) extends ConsumerWidget { @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 afdad89..4c0fdde 100644 --- a/lib/widgets/file_card.dart +++ b/lib/widgets/file_card.dart @@ -1,13 +1,13 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:nexus/helpers/extensions/size_to_string.dart"; import "package:nexus/models/info/file.dart"; -class FileCard extends StatelessWidget { - final Uri uri; - final FileInfo? info; - final String? filename; - const FileCard(this.uri, this.info, {this.filename, super.key}); - +final class const FileCard( + final Uri uri, + final FileInfo? info, { + final String? filename, + super.key, +}) extends StatelessWidget { @override Widget build(BuildContext context) => SizedBox( width: 320, diff --git a/lib/widgets/highlight_wrapper.dart b/lib/widgets/highlight_wrapper.dart index c7e568e..b10e3ff 100644 --- a/lib/widgets/highlight_wrapper.dart +++ b/lib/widgets/highlight_wrapper.dart @@ -1,10 +1,10 @@ -import "package:flutter/material.dart"; - -class HighlightWrapper extends StatelessWidget { - final Widget child; - final bool isHighlighted; - const HighlightWrapper(this.child, {this.isHighlighted = false, super.key}); +import "package:material_ui/material_ui.dart"; +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 a5c3dee..974b74d 100644 --- a/lib/widgets/html/code_block.dart +++ b/lib/widgets/html/code_block.dart @@ -1,12 +1,12 @@ import "dart:math"; -import "package:flutter/material.dart"; - -class CodeBlock extends StatelessWidget { - final String code; - final String lang; - const CodeBlock(this.code, {required this.lang, super.key}); +import "package:material_ui/material_ui.dart"; +class const CodeBlock( + final String code, { + required final String lang, + super.key, +}) extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); diff --git a/lib/widgets/html/html.dart b/lib/widgets/html/html.dart index 5437285..e4b8c01 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:flutter/material.dart"; +import "package:material_ui/material_ui.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,130 +11,139 @@ import "package:nexus/widgets/html/spoiler_text.dart"; import "package:nexus/widgets/html/code_block.dart"; import "package:nexus/widgets/html/quoted.dart"; -class Html extends ConsumerWidget { - final String html; - final String? roomId; - final TextStyle? textStyle; - const Html(this.html, {this.roomId, this.textStyle, super.key}); - +class const Html( + final String html, { + final String? roomId, + final TextStyle? textStyle, + super.key, +}) extends ConsumerWidget { @override - 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(); - } + 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(); + } - if (element.attributes.keys.contains("data-mx-spoiler")) { - return InlineCustomWidget(child: SpoilerText(text: element.text)); - } + if (element.attributes.keys.contains("data-mx-spoiler")) { + return InlineCustomWidget(child: SpoilerText(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), - ), - - "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), + "a" => + element.attributes["href"]?.mention == null + ? null + : InlineCustomWidget( + child: MentionChip(element.attributes["href"]!, roomId), ), - 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, + "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(), + ), + ), + ), - _ => 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)), - ); + // 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)), + ), + ); } diff --git a/lib/widgets/html/mention_chip.dart b/lib/widgets/html/mention_chip.dart index f6105e3..eb10e4b 100644 --- a/lib/widgets/html/mention_chip.dart +++ b/lib/widgets/html/mention_chip.dart @@ -1,40 +1,59 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 MentionChip extends ConsumerWidget { - final String? roomId; - final String content; - const MentionChip(this.content, this.roomId, {super.key}); - +class const MentionChip(final String content, final String? roomId, {super.key}) + extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final mention = content.mention; - final membership = mention?.startsWith("@") == true - ? ref - .watch( - UserController.provider(.new(roomId: roomId, userId: mention!)), - ) - .whenOrNull(data: (data) => data) - : null; + 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, + }; return mention == null ? SizedBox.shrink() : InkWell( onTap: () { - if (membership != null) { + if (data case MembershipContent membership) { context.showUserPopover(membership, mention, roomId: roomId); + } else if (data case RoomSummary summary) { + // TODO: Handle summary } }, child: IgnorePointer( child: Chip( label: Text( - (membership?.displayName == null - ? null - : "@${membership!.displayName}") ?? - mention, + 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.onPrimary, diff --git a/lib/widgets/html/quoted.dart b/lib/widgets/html/quoted.dart index e582b06..3157263 100644 --- a/lib/widgets/html/quoted.dart +++ b/lib/widgets/html/quoted.dart @@ -1,9 +1,6 @@ -import "package:flutter/material.dart"; - -class Quoted extends StatelessWidget { - final Widget child; - const Quoted(this.child, {super.key}); +import "package:material_ui/material_ui.dart"; +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 a7a457b..92a5538 100644 --- a/lib/widgets/html/spoiler_text.dart +++ b/lib/widgets/html/spoiler_text.dart @@ -1,11 +1,7 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -class SpoilerText extends HookWidget { - final String text; - - const SpoilerText({super.key, required this.text}); - +class const SpoilerText(final String text, {super.key}) extends HookWidget { @override Widget build(BuildContext context) { final revealed = useState(false); diff --git a/lib/widgets/join_dialog.dart b/lib/widgets/join_dialog.dart index 50b36f2..e818c7d 100644 --- a/lib/widgets/join_dialog.dart +++ b/lib/widgets/join_dialog.dart @@ -1,5 +1,5 @@ import "package:collection/collection.dart"; -import "package:flutter/material.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/client.dart"; @@ -7,10 +7,7 @@ import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/spaces.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; -class JoinDialog extends HookWidget { - final WidgetRef ref; - const JoinDialog(this.ref, {super.key}); - +class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget { @override Widget build(BuildContext context) { final roomAlias = useTextEditingController(); @@ -53,9 +50,8 @@ class JoinDialog extends HookWidget { .new( roomIdOrAlias: roomIdOrAlias, via: .new( - Uri.tryParse( - roomAlias.text.replaceAll("/#", ""), - )?.queryParametersAll["via"] ?? + Uri.tryParse(roomAlias.text.replaceAll("/#", "")) + ?.queryParametersAll["via"] ?? [], ), ), @@ -76,9 +72,8 @@ class JoinDialog extends HookWidget { await ref .watch( - KeyController.provider( - KeyController.spaceKey, - ).notifier, + KeyController.provider(KeyController.spaceKey) + .notifier, ) .set( space?.id ?? @@ -100,9 +95,8 @@ class JoinDialog extends HookWidget { if (space == null) { await ref .watch( - KeyController.provider( - KeyController.roomKey, - ).notifier, + KeyController.provider(KeyController.roomKey) + .notifier, ) .set(id); } @@ -115,9 +109,9 @@ class JoinDialog 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 93c2554..709d558 100644 --- a/lib/widgets/lazy_loading/message_avatar.dart +++ b/lib/widgets/lazy_loading/message_avatar.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 MessageAvatar extends ConsumerWidget { - final Event event; - final double height; - const MessageAvatar(this.event, {this.height = 24, super.key}); - +class const MessageAvatar( + final Event event, { + final double height = 24, + super.key, +}) extends ConsumerWidget { @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 bd5733c..9203f69 100644 --- a/lib/widgets/lazy_loading/message_displayname.dart +++ b/lib/widgets/lazy_loading/message_displayname.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/author.dart"; import "package:nexus/helpers/extensions/get_localpart.dart"; @@ -6,17 +6,12 @@ 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 MessageDisplayname extends ConsumerWidget { - final Event event; - final TextStyle? style; - final bool clickable; - const MessageDisplayname( - this.event, { - this.clickable = true, - this.style, - super.key, - }); - +class const MessageDisplayname( + final Event event, { + final TextStyle? style, + final bool clickable = true, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => switch (ref.watch( AuthorController.provider(event), @@ -40,14 +35,11 @@ class MessageDisplayname extends ConsumerWidget { 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 653248c..77ba7a4 100644 --- a/lib/widgets/linkified_text.dart +++ b/lib/widgets/linkified_text.dart @@ -1,14 +1,14 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_linkify/flutter_linkify.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/helpers/launch_helper.dart"; -class LinkifiedText extends ConsumerWidget { - final String text; - final int? maxLines; - final TextStyle? style; - const LinkifiedText(this.text, {this.maxLines, this.style, super.key}); - +final class const LinkifiedText( + final String text, { + final int? maxLines, + final TextStyle? style, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => Linkify( text: text, diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart index fc84563..180cd1b 100644 --- a/lib/widgets/loading.dart +++ b/lib/widgets/loading.dart @@ -1,9 +1,6 @@ -import "package:flutter/material.dart"; - -class Loading extends StatelessWidget { - final double? height; - const Loading({this.height, super.key}); +import "package:material_ui/material_ui.dart"; +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 92bb179..9ddafa3 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:m3e_buttons/m3e_buttons.dart"; @@ -16,10 +16,8 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/user_bottom_sheet.dart"; -class MemberList extends HookConsumerWidget { - final String roomId; - const MemberList(this.roomId, {super.key}); - +class const MemberList(final String roomId, {super.key}) + extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final statusIndex = useState(0); @@ -96,9 +94,9 @@ class MemberList extends HookConsumerWidget { ), 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 914a252..b04c209 100644 --- a/lib/widgets/message_image.dart +++ b/lib/widgets/message_image.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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"; -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}); - +final class const MessageImage( + final Uri url, { + final i.ImageInfo? info, + required final bool encrypted, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final request = DownloadMediaRequest(mxc: url, encrypted: encrypted); diff --git a/lib/widgets/pinned_events_drawer.dart b/lib/widgets/pinned_events_drawer.dart index 7cb9fe3..046e231 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/pinned_events.dart"; import "package:nexus/models/event.dart"; @@ -7,17 +7,12 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/renderers/event.dart"; -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, - }); - +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 { @override Widget build(BuildContext context, WidgetRef ref) { final pinsProvider = ref.watch(PinnedEventsController.provider(roomId)); diff --git a/lib/widgets/players/audio.dart b/lib/widgets/players/audio.dart index 0c96579..bd50a8b 100644 --- a/lib/widgets/players/audio.dart +++ b/lib/widgets/players/audio.dart @@ -1,18 +1,18 @@ import "dart:async"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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/helpers/extensions/get_headers.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/audio.dart"; -class AudioPlayer extends HookConsumerWidget { - final Uri url; - final AudioInfo? info; - - const AudioPlayer(this.url, this.info, {super.key}); - +class const AudioPlayer( + final Uri uri, + final AudioInfo? info, { + final bool encrypted = false, + super.key, +}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final player = useMemoized( @@ -24,11 +24,12 @@ class AudioPlayer extends HookConsumerWidget { final duration = useState(Duration.zero); useEffect(() { + player.platform?.state = player.platform!.state.copyWith(buffering: true); scheduleMicrotask(() async { - await player.open( - Media(url.toString(), httpHeaders: ref.headers), - play: false, - ); + final video = await ref + .watch(ClientController.provider.notifier) + .downloadMedia(.new(mxc: uri, encrypted: encrypted)); + await player.open(Media(video.path), play: false); player.stream.playing.listen((value) { playing.value = value; @@ -67,12 +68,18 @@ class AudioPlayer extends HookConsumerWidget { padding: .only(left: 8, right: 16), child: Row( children: [ - IconButton( - onPressed: player.playOrPause, - icon: Icon( - playing.value ? Icons.pause_circle : Icons.play_circle, + if (player.state.buffering) + SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(padding: .all(4)), + ) + else + IconButton( + onPressed: player.playOrPause, + icon: Icon( + playing.value ? Icons.pause_circle : Icons.play_circle, + ), ), - ), SizedBox(width: 8), Text( format(position.value), diff --git a/lib/widgets/players/video.dart b/lib/widgets/players/video.dart index 8083860..024c30e 100644 --- a/lib/widgets/players/video.dart +++ b/lib/widgets/players/video.dart @@ -1,18 +1,19 @@ import "dart:async"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/video.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:media_kit/media_kit.dart"; import "package:media_kit_video/media_kit_video.dart"; -import "package:nexus/helpers/extensions/get_headers.dart"; - -class VideoPlayer extends HookConsumerWidget { - final VideoInfo? info; - final Uri url; - const VideoPlayer(this.url, this.info, {super.key}); +class const VideoPlayer( + final Uri uri, + final VideoInfo? info, { + final bool encrypted = false, + super.key, +}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final player = useMemoized( @@ -21,12 +22,13 @@ class VideoPlayer extends HookConsumerWidget { final controller = useMemoized(() => VideoController(player)); useEffect(() { - scheduleMicrotask( - () => player.open( - Media(url.toString(), httpHeaders: ref.headers), - play: false, - ), - ); + player.platform?.state = player.platform!.state.copyWith(buffering: true); + scheduleMicrotask(() async { + final video = await ref + .watch(ClientController.provider.notifier) + .downloadMedia(.new(mxc: uri, encrypted: encrypted)); + await player.open(Media(video.path), play: false); + }); return player.dispose; }, []); diff --git a/lib/widgets/reaction_row.dart b/lib/widgets/reaction_row.dart index b2fa5ca..56f2356 100644 --- a/lib/widgets/reaction_row.dart +++ b/lib/widgets/reaction_row.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state.dart"; @@ -10,10 +10,7 @@ import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/main.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -class ReactionRow extends ConsumerWidget { - final Event event; - const ReactionRow(this.event, {super.key}); - +class const ReactionRow(final Event event, {super.key}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final clientState = ref.watch(ClientStateController.provider); @@ -67,9 +64,8 @@ class ReactionRow extends ConsumerWidget { enabled.value = false; try { final controller = ref.watch( - RoomChatController.provider( - event.roomId, - ).notifier, + RoomChatController.provider(event.roomId) + .notifier, ); if (selected) { diff --git a/lib/widgets/renderers/event.dart b/lib/widgets/renderers/event.dart index bcca961..7607c2a 100644 --- a/lib/widgets/renderers/event.dart +++ b/lib/widgets/renderers/event.dart @@ -1,5 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.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/helpers/extensions/show_context_menu.dart"; @@ -25,23 +25,15 @@ import "package:nexus/widgets/reaction_row.dart"; import "package:nexus/widgets/renderers/membership.dart"; import "package:nexus/widgets/renderers/generic_event.dart"; -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, - }); - +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 { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -202,9 +194,8 @@ class EventRenderer extends HookConsumerWidget { onSecondaryTapUp: contextMenuCallback, onLongPressStart: contextMenuCallback, child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 8, - ).copyWith(top: isGrouped ? 0 : 8), + padding: EdgeInsets.symmetric(horizontal: 8) + .copyWith(top: isGrouped ? 0 : 8), child: child, ), ), @@ -213,13 +204,19 @@ class EventRenderer extends HookConsumerWidget { ), ...[ - if (event.content is! MessageContent) 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") - Text( - event.sendError!, - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.error, + Padding( + padding: .only(bottom: 4), + child: Text( + event.sendError!, + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.error, + ), ), ), ].map((child) => Padding(padding: .only(left: 4), child: child)), diff --git a/lib/widgets/renderers/generic_event.dart b/lib/widgets/renderers/generic_event.dart index 6dfae53..88d2db8 100644 --- a/lib/widgets/renderers/generic_event.dart +++ b/lib/widgets/renderers/generic_event.dart @@ -1,10 +1,10 @@ -import "package:flutter/material.dart"; - -class GenericEventRenderer extends StatelessWidget { - final IconData icon; - final List children; - const GenericEventRenderer(this.icon, this.children, {super.key}); +import "package:material_ui/material_ui.dart"; +class const GenericEventRenderer( + final IconData icon, + final List children, { + super.key, +}) extends StatelessWidget { @override Widget build(BuildContext context) => Padding( padding: .symmetric(vertical: 4), diff --git a/lib/widgets/renderers/membership.dart b/lib/widgets/renderers/membership.dart index b2835b4..5ce5d68 100644 --- a/lib/widgets/renderers/membership.dart +++ b/lib/widgets/renderers/membership.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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,10 +7,8 @@ import "package:nexus/models/event.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/renderers/generic_event.dart"; -class MembershipRenderer extends StatelessWidget { - final Event event; - const MembershipRenderer(this.event, {super.key}); - +class const MembershipRenderer(final Event event, {super.key}) + extends StatelessWidget { @override Widget build(BuildContext context) { assert( diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 6d660af..8631fe2 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -1,5 +1,5 @@ import "package:collection/collection.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:linkify/linkify.dart"; import "package:nexus/controllers/client_state.dart"; @@ -21,21 +21,14 @@ import "package:nexus/widgets/event_preview.dart"; import "package:nexus/widgets/players/video.dart"; import "package:nexus/widgets/players/audio.dart"; -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, - }); - +class const MessageRenderer( + final Event event, { + final VoidCallback? onTapReply, + final bool textOnly = false, + final bool isGrouped = false, + final int? maxLines, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -226,10 +219,25 @@ class MessageRenderer extends ConsumerWidget { ConstrainedBox( constraints: .loose(.square(500)), child: switch (event.content) { - VideoMessageContent(:final info) => - VideoPlayer(url, info), - AudioMessageContent(:final info) => - AudioPlayer(url, info), + 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, @@ -258,9 +266,8 @@ class MessageRenderer extends ConsumerWidget { (element) => element is UrlElement, ) case final UrlElement link?) - UrlPreview(link.url), - - ReactionRow(event), + if (Uri.tryParse(link.url) case final Uri url?) + UrlPreview(url), ], ], ), @@ -270,9 +277,8 @@ class MessageRenderer extends ConsumerWidget { "This message is redacted", style: errorStyle, ) - : Row( + : Wrap( spacing: 8, - mainAxisSize: .min, children: [ Text( "Unknown message type:", @@ -283,6 +289,7 @@ class MessageRenderer extends ConsumerWidget { ), _ => throw Exception("This is impossible"), }, + if (!textOnly) ReactionRow(event), ], ), ), diff --git a/lib/widgets/room_appbar.dart b/lib/widgets/room_appbar.dart index 93eca85..305c490 100644 --- a/lib/widgets/room_appbar.dart +++ b/lib/widgets/room_appbar.dart @@ -1,4 +1,4 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/widgets/appbar.dart"; @@ -7,21 +7,14 @@ import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/linkified_text.dart"; import "package:nexus/widgets/room_menu.dart"; -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, - }); - +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 { @override Size get preferredSize => AppBar().preferredSize; @@ -40,48 +33,52 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget { constraints: .loose(.fromWidth(400)), child: Padding( padding: .all(24), - 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, + 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( room.metadata?.name ?? "Unnamed Room", - height: 64, - fallback: Icon(Icons.numbers), + overflow: .ellipsis, + maxLines: 3, + style: Theme.of(context) + .textTheme + .headlineSmall, ), ), - 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, - ), + ], ), - ], + 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 index 0fdd7c7..456109e 100644 --- a/lib/widgets/room_chat.dart +++ b/lib/widgets/room_chat.dart @@ -1,5 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.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"; @@ -7,6 +7,7 @@ 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"; @@ -27,17 +28,12 @@ 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, - }); - +final class const RoomChat({ + required final String? roomId, + required final bool isDesktop, + required final bool showMembersByDefault, + super.key, +}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final relatedEvent = useState(null); @@ -46,9 +42,8 @@ class RoomChat extends HookConsumerWidget { final composerSize = useState(64); - final memberListOpened = useState(showMembersByDefault); - final userId = ref.watch(ClientStateController.provider)?.userId; + final memberListOpened = ref.watch(MemberListOpenedController.provider); final theme = Theme.of(context); final nothing = Center( @@ -80,6 +75,7 @@ class RoomChat extends HookConsumerWidget { final controllerData = ref.watch(controllerProvider); final topEventBeforeLoad = useState(null); + final hasMore = useState(true); Future jumpToId(String eventId) async { final index = controllerData.value?.indexWhere( @@ -105,7 +101,7 @@ class RoomChat extends HookConsumerWidget { Future loadOlder() async { if (controllerData case AsyncData(:final value?)) { topEventBeforeLoad.value = value.firstOrNull?.eventId; - await notifier.loadOlder(); + hasMore.value = await notifier.loadOlder(); } } @@ -126,9 +122,8 @@ class RoomChat extends HookConsumerWidget { }, [scrollController.hasClients]); useEffect(() { - if (controllerData case AsyncData( - :final value?, - ) when scrollController.hasClients) { + if (controllerData case AsyncData(:final value?) + when scrollController.hasClients) { if (topEventBeforeLoad.value != null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (scrollController.hasClients) { @@ -214,11 +209,9 @@ class RoomChat extends HookConsumerWidget { ...{ ...ref.watch( AccountDataController.provider.select( - (value) => IList( - value["m.recent_emoji"] - ?.content["recent_emoji"] ?? - [], - ).map((entry) => entry["emoji"]).toIList(), + (value) => value.recentEmoji + .map((entry) => entry.emoji) + .toIList(), ), ), "👍", @@ -312,7 +305,7 @@ class RoomChat extends HookConsumerWidget { await Clipboard.setData( ClipboardData( text: - "matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias)", + "matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias", ), ); }, @@ -448,7 +441,9 @@ class RoomChat extends HookConsumerWidget { isDesktop: isDesktop, onOpenDrawer: Scaffold.of(context).openDrawer, onOpenMemberList: (thisContext) { - memberListOpened.value = !memberListOpened.value; + ref + .watch(MemberListOpenedController.provider.notifier) + .set(!memberListOpened); Scaffold.of(thisContext).openEndDrawer(); }, onOpenPinnedMessagesList: () { @@ -466,21 +461,24 @@ class RoomChat extends HookConsumerWidget { child: switch (controllerData) { AsyncData(:final value?) || AsyncLoading(:final value?) => CustomScrollView( + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, controller: scrollController, slivers: [ - SliverToBoxAdapter( - child: Padding( - padding: .symmetric(vertical: 36), - child: Center( - child: ElevatedButton( - onPressed: controllerData is AsyncData - ? loadOlder - : null, - child: Text("Load More"), + if (hasMore.value) + 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, @@ -553,7 +551,7 @@ class RoomChat extends HookConsumerWidget { ), ), - if (memberListOpened.value == true && showMembersByDefault) + if (memberListOpened == true && showMembersByDefault) MemberList(roomId), ], ), diff --git a/lib/widgets/room_menu.dart b/lib/widgets/room_menu.dart index 95936df..278e479 100644 --- a/lib/widgets/room_menu.dart +++ b/lib/widgets/room_menu.dart @@ -1,16 +1,16 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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"; -class RoomMenu extends ConsumerWidget { - final Room? room; - final IList children; - const RoomMenu(this.room, {this.children = const IList.empty(), super.key}); - +final class const RoomMenu( + final Room? room, { + final IList children = const IList.empty(), + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final danger = Theme.of(context).colorScheme.error; @@ -35,8 +35,7 @@ class RoomMenu extends ConsumerWidget { 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 f0a236c..e66e335 100644 --- a/lib/widgets/settings/dialog_list_tile.dart +++ b/lib/widgets/settings/dialog_list_tile.dart @@ -1,28 +1,18 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/widgets/settings/radio_dialog.dart"; -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, - }); - +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 { @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 7695b9d..6696139 100644 --- a/lib/widgets/settings/radio_dialog.dart +++ b/lib/widgets/settings/radio_dialog.dart @@ -1,21 +1,14 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -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, - }); - +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 { @override Widget build(BuildContext context) { final mutValue = useState(null); diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index 1bd6cf5..6473987 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -1,6 +1,6 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/key.dart"; @@ -12,10 +12,11 @@ import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/join_dialog.dart"; import "package:nexus/widgets/room_menu.dart"; -class Sidebar extends HookConsumerWidget { - final bool isDesktop; - const Sidebar({required this.isDesktop, super.key}); +// Needed for navigation_rail_m3e (#65). +import "package:flutter/material.dart" as old_mat; +class const Sidebar({required final bool isDesktop, super.key}) + extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedSpaceProvider = KeyController.provider( @@ -74,126 +75,137 @@ class Sidebar extends HookConsumerWidget { shape: Border(), child: Row( children: [ - Theme( - data: Theme.of(context).copyWith( - extensions: [ - NavigationRailM3ETheme( - itemCollapsedHeight: 48, - itemVerticalGap: 0, + MaterialUiCompatibilityBridge( + child: Builder( + builder: (context) => old_mat.Theme( + data: old_mat.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 settings", - onPressed: () => showDialog( - context: context, - builder: (_) => SettingsPage(), - ), - icon: Icon(Icons.settings), + 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 settings", + onPressed: () => showDialog( + context: context, + builder: (_) => SettingsPage(), + ), + icon: Icon(Icons.settings), + ), + ], + ), + ), ), ), ), ), ), + Expanded( child: Scaffold( backgroundColor: Colors.transparent, @@ -219,67 +231,75 @@ class Sidebar extends HookConsumerWidget { ), ], ), - body: Theme( - data: Theme.of(context).copyWith( - extensions: [ - NavigationRailM3ETheme( - itemExpandedHeight: 48, - iconLabelGap: 16, + body: MaterialUiCompatibilityBridge( + child: Builder( + builder: (context) => old_mat.Theme( + data: old_mat.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, ), ), - 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(); - }, + 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(); + }, + ), + ), ), ), ), diff --git a/lib/widgets/url_preview.dart b/lib/widgets/url_preview.dart index 5831300..37a2733 100644 --- a/lib/widgets/url_preview.dart +++ b/lib/widgets/url_preview.dart @@ -1,14 +1,11 @@ -import "package:flutter/material.dart"; +import "package:material_ui/material_ui.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 UrlPreview extends ConsumerWidget { - final String link; - const UrlPreview(this.link, {super.key}); - +class const UrlPreview(final Uri link, {super.key}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => ConstrainedBox( constraints: .loose(.fromWidth(400)), @@ -18,13 +15,12 @@ class UrlPreview extends ConsumerWidget { data: (preview) => preview == null ? SizedBox.shrink() : InkWell( - onTap: () => - ref.watch(LaunchHelper.provider).launchUrl(.parse(link)), + 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( diff --git a/lib/widgets/user_bottom_sheet.dart b/lib/widgets/user_bottom_sheet.dart index 6f9f154..a725613 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:flutter/material.dart"; +import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; @@ -16,12 +16,12 @@ import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/main.dart"; import "package:nexus/widgets/expandable_image.dart"; -class UserBottomSheet extends ConsumerWidget { - final MembershipContent member; - final String userId; - final String? roomId; - const UserBottomSheet(this.member, this.userId, {this.roomId, super.key}); - +final class const UserBottomSheet( + final MembershipContent member, + final String userId, { + final String? roomId, + super.key, +}) extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); @@ -123,9 +123,10 @@ class UserBottomSheet extends ConsumerWidget { .watch(ProfileController.provider(userId)) .betterWhen( loading: () => Text(""), - data: (profile) => Column( + data: (profileResponse) => Column( children: [ - if (profile.timezone == null && profile.pronouns.isEmpty) + if (profileResponse.profile.timezone == null && + profileResponse.profile.pronouns.isEmpty) Text(""), Wrap( crossAxisAlignment: .center, @@ -133,7 +134,7 @@ class UserBottomSheet extends ConsumerWidget { spacing: 4, runSpacing: 4, children: [ - ...profile.pronouns + ...profileResponse.profile.pronouns .where( // TODO: Check system language (l10n) (pronoun) => pronoun.language == "en", @@ -157,8 +158,8 @@ class UserBottomSheet extends ConsumerWidget { ) .flattened, - if (profile.timezone != null) ...[ - if (profile.pronouns.isNotEmpty) + if (profileResponse.profile.timezone != null) ...[ + if (profileResponse.profile.pronouns.isNotEmpty) SizedBox( height: 16, child: VerticalDivider( @@ -168,7 +169,7 @@ class UserBottomSheet extends ConsumerWidget { ), ), Text( - profile.timezone!, + profileResponse.profile.timezone!, textAlign: .center, style: textTheme.titleSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix index ae77467..dd3c78e 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 = "36.0.1"; + platformToolsVersion = "37.0.1"; buildToolsVersions = [ "35.0.0" "36.0.0" @@ -18,31 +18,34 @@ let }; in pkgs.mkShell { - packages = with pkgs; [ - go - git - jdk17 - libGL - wayland - (flutter.override { - extraPkgConfigPackages = [ - mpv-unwrapped - libass - ]; - }) - android.platform-tools - ]; + packages = + with pkgs; + [ + go + git + + libGL + (flutter.override { + extraPkgConfigPackages = [ + mpv-unwrapped + libass + ]; + }) + android.platform-tools + ] + ++ lib.optional pkgs.stdenv.isLinux wayland; env = rec { LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ]; - LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; - CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ]; ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; ANDROID_SDK_ROOT = ANDROID_HOME; - JAVA_HOME = pkgs.jdk17; + JAVA_HOME = pkgs.jdk21; - TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}"; - GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2"; + GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/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/pkg/default.nix b/linux/nix/pkg/default.nix index 4e00959..1114d43 100644 --- a/linux/nix/pkg/default.nix +++ b/linux/nix/pkg/default.nix @@ -35,7 +35,7 @@ flutter.buildFlutterApplication { }; postInstall = '' - install -D assets/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg + install -D assets/bundled/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 @@ -44,7 +44,7 @@ flutter.buildFlutterApplication { meta = { description = "A simple and user-friendly Matrix client"; mainProgram = "nexus"; - platforms = lib.platforms.linux; + platforms = lib.platforms.unix; maintainers = with lib.maintainers; [ quadradical ]; }; } diff --git a/linux/nix/pkg/gomuks.nix b/linux/nix/pkg/gomuks.nix index 40fe29c..ffa9d7c 100644 --- a/linux/nix/pkg/gomuks.nix +++ b/linux/nix/pkg/gomuks.nix @@ -1,31 +1,38 @@ { src, + stdenv, buildGoModule, }: -buildGoModule (finalAttrs: { - pname = "gomuks-ffi"; - version = "submodule"; +buildGoModule ( + finalAttrs: + let + filename = "libgomuks${stdenv.hostPlatform.extensions.sharedLibrary}"; + in + { + pname = "gomuks-ffi"; + version = "submodule"; - doCheck = false; + doCheck = false; - src = "${src}/gomuks"; + src = "${src}/gomuks"; - vendorHash = "sha256-4QU51WGiNKYm7z/ajnas2qQaMK5BgyHwPfmNpQvW0qg="; + vendorHash = "sha256-C03ss88QAnmZu+XxoHhc1M/261xFl8hR/pzLbU37Qe8="; - buildPhase = '' - runHook preBuild + buildPhase = '' + runHook preBuild - go build -buildmode=c-shared -o libgomuks.so -tags goolm,noheic,sqlite_fts5 ./pkg/ffi + go build -buildmode=c-shared -o ${filename} -tags goolm,noheic,sqlite_fts5 ./pkg/ffi - runHook postBuild - ''; + runHook postBuild + ''; - installPhase = '' - runHook preInstall + installPhase = '' + runHook preInstall - install -Dm0644 libgomuks.so -t $out/lib + install -Dm0644 ${filename} -t $out/lib - runHook postInstall - ''; -}) + runHook postInstall + ''; + } +) diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#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 c2efd0b..5caa9d1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..65543e2 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..08cb773 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,28 @@ +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 f87523c..fa18d44 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,9 @@ 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 */ @@ -60,11 +63,12 @@ /* 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 = ""; }; @@ -76,8 +80,16 @@ 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 */ @@ -85,6 +97,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 34AEA7295333B7F48CD3BE4C /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +105,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + BAF79690465DBAC1A1E19D28 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +140,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + FC780AF47288088B9004ACE2 /* Pods */, ); sourceTree = ""; }; @@ -151,6 +167,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -175,10 +192,26 @@ 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 */ @@ -186,6 +219,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + C413A8A75594D2165C9D56BD /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +238,13 @@ 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 = ( ); @@ -216,6 +252,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* nexus.app */; productType = "com.apple.product-type.application"; @@ -260,6 +299,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -329,6 +371,67 @@ 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 */ @@ -380,6 +483,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 18EDDEF1FB58EC56ABDA1BEB /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +498,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8F5ECEC7BF9DD35CB3E7196E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +513,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5099E6191AE87E21D356D9C8 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -461,7 +567,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -543,7 +649,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -593,7 +699,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -700,6 +806,20 @@ 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 ff47eeb..746b383 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + + + diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f..96d3fee 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "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" - } -} + "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 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 82b6f9d..1682af0 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 13b35eb..c2e488b 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 0a3f5fa..e2bf256 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 bdb5722..983d7fa 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 f083318..c8f0676 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 326c0e7..468be1f 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 2f1632c..a77207d 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 c989ed4..d45c4d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" url: "https://pub.dev" source: hosted - version: "99.0.0" + version: "103.0.0" analysis_server_plugin: dependency: transitive description: name: analysis_server_plugin - sha256: "3960b28ee740004df39f85d5ebfc91785f7a90e51fd7c9a185e86a36b2f581b4" + sha256: c58b2a05b260a023eefd0848496ff6b113a318c6c95dad4215165a4bd54ae600 url: "https://pub.dev" source: hosted - version: "0.3.14" + version: "0.3.18" analyzer: dependency: transitive description: name: analyzer - sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "13.3.0" analyzer_buffer: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: analyzer_plugin - sha256: "0057a98d64d7bb872b0c87dff6e73d2c2d80c77156e7a03f127a26f8aa240649" + sha256: c2a75baf1657171cc7872db96a12b07e385873cf7a73f2b4f7638e9d0701ee53 url: "https://pub.dev" source: hosted - version: "0.14.8" + version: "0.14.12" app_links: dependency: "direct main" description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: archive - sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + sha256: be169cf6ac481e052c4538715d88841d567150dfe1df38aaec76461a4e7b39f2 url: "https://pub.dev" source: hosted - version: "4.0.9" + version: "4.1.0" args: dependency: transitive description: @@ -89,6 +89,14 @@ 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: @@ -109,34 +117,34 @@ packages: dependency: transitive description: name: build - sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" + sha256: b94f5da9ed3d081fd4ecc426c260998b5f4f4eb2f6d89b7e5472edef0b2b2a1b url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.10" build_config: dependency: transitive description: name: build_config - sha256: f2c223156a26eea323e6244b85141d76413a80aeee9fe0b380773789fabaf8ae + sha256: "94eaf6708fe64408c632ef2689ca3777b112f9421306ccf4f8c84d7c5c9f83f8" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + sha256: "79e05eaf15a48d7230b053a4363b8eaac0cc234bbd0134c3229455481f55cbc6" url: "https://pub.dev" source: hosted - version: "4.1.2" + version: "4.1.5" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" + sha256: "90363d438bd9f84a4d5bbb83352251f57d2d9d771bc95a44e6a33fe25fa52774" url: "https://pub.dev" source: hosted - version: "2.15.1" + version: "2.16.0" built_collection: dependency: transitive description: @@ -149,10 +157,10 @@ packages: dependency: transitive description: name: built_value - sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" url: "https://pub.dev" source: hosted - version: "8.12.6" + version: "8.12.7" button_m3e: dependency: transitive description: @@ -169,14 +177,6 @@ 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: @@ -273,38 +273,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: "7ed8ce4159d342eec4c65f4ea6eec57adaf9365404378541f38efc1da20a5b3d" + url: "https://pub.dev" + source: hosted + version: "1.0.0" dart_style: dependency: transitive description: name: dart_style - sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 + sha256: "3f88fc9c96c568d631356507355a2d1e983ee000c9ac008bdcb39b5bf53ce777" url: "https://pub.dev" source: hosted - version: "3.1.8" + version: "3.1.12" dbus: dependency: transitive description: name: dbus - sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 url: "https://pub.dev" source: hosted - version: "0.7.14" + version: "0.7.15" dynamic_color: dependency: "direct main" description: name: dynamic_color - sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" + sha256: "869b3bce0100eb519768ecd71c0111f0b5ea679f5f62e1f59c49d80067e730c5" url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "2.1.0" dynamic_polls: dependency: "direct main" description: name: dynamic_polls - sha256: "72ff19cdf041ad8dcfa76adaebb216d005f40b278d955e6e0c7bcb769215fabe" + sha256: "58cd1fafbbaba6b485a61692aa3bd33ded6f0f61734543b6c754511c362d8ae1" url: "https://pub.dev" source: hosted - version: "0.0.7" + version: "0.0.8" emoji_text_field: dependency: "direct main" description: @@ -314,6 +322,14 @@ packages: url: "https://github.com/Henry-Hiles/emoji_text_field" source: git version: "1.0.0" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" equatable: dependency: transitive description: @@ -366,10 +382,10 @@ packages: dependency: "direct main" description: name: ffigen - sha256: b7803707faeec4ce3c1b0c2274906504b796e3b70ad573577e72333bd1c9b3ba + sha256: "56fc0be88937409839a59c133dfdee4165e80da324c9e39f59013060249ffb44" url: "https://pub.dev" source: hosted - version: "20.1.1" + version: "21.0.0" file: dependency: transitive description: @@ -390,10 +406,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b" + sha256: "1d45e9910f68c16eb0c74f0b10097ad81aed516ea28054c027137e8f7d75e840" url: "https://pub.dev" source: hosted - version: "0.5.2+8" + version: "0.5.2+9" file_selector_ios: dependency: transitive description: @@ -512,10 +528,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e" + sha256: "56e81e662e6e54e59b231da57e90ac0d06ac67d8e3f2273c129a37ae6282b40c" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.2" flutter_svg: dependency: "direct main" description: @@ -554,10 +570,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "8599ba37236328ff6f97269ecce875d251a367eb2929c9bbf9b811b2ce56c74e" + sha256: "27585a6c2b9ac28ffb7b45d87fd1502332af1eb90e1c9f5eb1acb24c71d26e17" url: "https://pub.dev" source: hosted - version: "3.2.6-dev.1" + version: "4.0.0" freezed_annotation: dependency: "direct main" description: @@ -578,10 +594,10 @@ packages: dependency: transitive description: name: get_x_storage - sha256: "69e4412dd70e25a4991623c10bf72e3b12106f2cb4353a2d167353947597f3aa" + sha256: "597cd8f6ef8b01c747a6d95ff7b0310066f6c82ddf5565ca371f047f316fe43e" url: "https://pub.dev" source: hosted - version: "0.0.9" + version: "0.2.0" glob: dependency: transitive description: @@ -610,18 +626,18 @@ packages: dependency: "direct main" description: name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.2.0" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - sha256: dcaf59f0f41489ff08ce61438ada564d67f40cfa37cc7c8589da78fb600c2edc + sha256: fce693abc6289c8bb42348eae520b979f7edbaae0c1b77b946ef058d0693f6f6 url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.2" html: dependency: transitive description: @@ -666,10 +682,10 @@ packages: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: "1976370a4df3091bb0f72409c187ad1f9132a818bc6b95ca59c0bae1c75c688e" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.2" image_picker: dependency: "direct main" description: @@ -738,10 +754,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" io: dependency: transitive description: @@ -750,6 +766,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -762,10 +786,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 + sha256: e45aefa0324f08c683caafbb94b72837aa6193c61822799c916e45f4a263113d url: "https://pub.dev" source: hosted - version: "6.14.0" + version: "6.14.1" leak_tracker: dependency: transitive description: @@ -807,6 +831,14 @@ 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: @@ -819,18 +851,18 @@ packages: dependency: "direct main" description: name: m3e_buttons - sha256: af1b19bd5ec7327d20b5a57c6c91e1ed30ee15a4dbade781c0cdbc72e6b04b5e + sha256: "001031f1b72ef19643a8a6a531aafcfb31a06b53456b6f60014403c125e67056" url: "https://pub.dev" source: hosted - version: "0.0.4" + version: "1.0.0" m3e_card_list: dependency: "direct main" description: name: m3e_card_list - sha256: d4aba0123cccda40ac80789befa8d355e1dc16aa7dcee910157690b0546d78d6 + sha256: ed1cc150f051b8812ffd735915ae59fed23517ffeeed04c4b2b8ef6257cc18b7 url: "https://pub.dev" source: hosted - version: "0.1.0" + version: "1.0.0" m3e_design: dependency: transitive description: @@ -843,10 +875,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -855,6 +887,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_ui: + dependency: "direct main" + description: + name: material_ui + sha256: "4f3f38b9953df0a87d6bf5f21880029f77c47048487d5339410c39936be4683b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" measure_size: dependency: "direct main" description: @@ -931,10 +971,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" mime: dependency: transitive description: @@ -951,12 +991,20 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: a1c26117c48cebe5677b0cf0e33a980a79a7c5577effc86f52e5a0d309cdcb60 + url: "https://pub.dev" + source: hosted + version: "0.19.3" navigation_rail_m3e: dependency: "direct main" description: path: "packages/navigation_rail_m3e" ref: HEAD - resolved-ref: "667b0bc8526fd53296778903b6ef3f22424f3aa4" + resolved-ref: da860a413d5cf78b9c9c967215250bcdbfe5191d url: "https://github.com/Henry-Hiles/material_3_expressive" source: git version: "0.3.5" @@ -972,10 +1020,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e url: "https://pub.dev" source: hosted - version: "9.4.1" + version: "9.5.0" package_config: dependency: transitive description: @@ -1088,6 +1136,14 @@ 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: @@ -1100,10 +1156,10 @@ packages: dependency: transitive description: name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.5.2" pub_semver: dependency: transitive description: @@ -1132,34 +1188,34 @@ packages: dependency: transitive description: name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "1.1.1" riverpod: dependency: transitive description: name: riverpod - sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76" + sha256: "84351b3472a447f7b89f961f95c73287009d58c59d8cc1267425d995f78e50f1" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.2" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "3e275138862ccc22ed61444a1f9a840f753094c367f28f4123f50289cd204d68" + sha256: "46a8dd0461080fde8f886f65f87580758dae8495e352f9ea6a09953abc3df7bd" url: "https://pub.dev" source: hosted - version: "1.0.0-dev.10" + version: "1.0.0-dev.11" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "166f29492228dc471b6fe294092560ccd5545ed15c9fc155622455b58b2451a9" + sha256: "7d37414a82b0f52f3d8e440320830d9a8c59620663884b2bb5baaca26296c995" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.8" rxdart: dependency: transitive description: @@ -1313,18 +1369,18 @@ packages: dependency: transitive description: name: source_gen - sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.2.4" source_helper: dependency: transitive description: name: source_helper - sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" + sha256: "5e6f216fdf6376c9f3852381ae037499797a3385377d388b011dac98d303c67c" url: "https://pub.dev" source: hosted - version: "1.3.12" + version: "1.3.13" source_map_stack_trace: dependency: transitive description: @@ -1337,10 +1393,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + sha256: "14c2945847669b44089bb1222f66873d7ff7103c58911917f2a63c5a62327898" url: "https://pub.dev" source: hosted - version: "0.10.13" + version: "0.10.14" source_span: dependency: transitive description: @@ -1401,10 +1457,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" + sha256: "3a7b5d17422dd0f8d5c6c14feaa5a1c65638b9455f871a96f08437562c046931" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.4.1+2" term_glyph: dependency: transitive description: @@ -1417,26 +1473,26 @@ packages: dependency: transitive description: name: test - sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f url: "https://pub.dev" source: hosted - version: "1.31.0" + version: "1.31.1" test_api: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" test_core: dependency: transitive description: name: test_core - sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5 url: "https://pub.dev" source: hosted - version: "0.6.17" + version: "0.6.18" timeago: dependency: "direct main" description: @@ -1453,22 +1509,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - universal_html: - dependency: transitive - description: - name: universal_html - sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 - url: "https://pub.dev" - source: hosted - version: "2.3.1" universal_platform: dependency: transitive description: @@ -1561,10 +1601,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" vector_graphics_codec: dependency: transitive description: @@ -1577,42 +1617,42 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" + sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" vm_service: dependency: transitive description: name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" url: "https://pub.dev" source: hosted - version: "15.2.0" + version: "15.3.0" wakelock_plus: dependency: transitive description: name: wakelock_plus - sha256: "824c5bba0f800e86d32e57d3d1843c531f090005cc89d9a837933e6601093d53" + sha256: "7253bca0fcf40d8413ddfcf4d2a1fa0a82475e79be25a4f2c564b695c9351486" url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "1.7.0" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b + sha256: "0618d1799f0b28bcf98255b4ee8313e6fc4d38589dc4ee5fe5840d57d1aff6da" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.6.0" watcher: dependency: transitive description: @@ -1657,10 +1697,10 @@ packages: dependency: transitive description: name: win32 - sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.0" window_manager: dependency: "direct main" description: @@ -1681,10 +1721,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" yaml: dependency: transitive description: @@ -1702,5 +1742,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: ">=3.12.2 <4.0.0" - flutter: ">=3.44.0" + dart: "3.13.0" + flutter: ">=3.47.0" diff --git a/pubspec.yaml b/pubspec.yaml index 72748b1..a9b2e01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,98 +1,103 @@ name: nexus -description: "Yet another Matrix client" +description: "A simple and user-friendly 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/ - 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/bundled/ + uses-material-design: true environment: - sdk: "^3.12.2" + sdk: 3.13.0 dependency_overrides: - path_provider_android: 2.2.23 # Pinned to avoid JNI - linkify: - git: - url: https://github.com/appelladev/linkify - ref: fix/consecutive-periods-loose-url + dynamic_color: 2.1.0 + 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.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 - + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_riverpod: 3.4.2 + hooks_riverpod: 3.4.2 + 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.2 + 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: 1.2.1 + ffigen: 21.0.0 + 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: 1.0.0 + 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.0.1 + dev_dependencies: - 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 + build_runner: 2.16.0 + flutter_lints: 6.0.0 + freezed: 4.0.0 + riverpod_lint: 3.1.8 + flutter_launcher_icons: 0.14.4 + json_serializable: 6.14.1 flutter_launcher_icons: - 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 + 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 diff --git a/scripts/generate.dart b/scripts/generate.dart index 0b0cf7c..4dc7329 100644 --- a/scripts/generate.dart +++ b/scripts/generate.dart @@ -17,14 +17,25 @@ void main(List args) async { entryPoints: [File(join(repoDir.path, "pkg", "ffi", "gomuksffi.h")).uri], compilerOptions: [ "--no-warnings", - if (Platform.isMacOS) "-I${await getXCodeSDK()}/usr/include", + if (Platform.isMacOS) "-I${await getXCodeTool()}/usr/include", ], ), functions: Functions.includeAll, ).generate( libclangDylib: libclangPath == null ? null - : Uri.file(join(libclangPath, "libclang.so")), + : Uri.file( + join( + libclangPath, + "libclang.${(Platform.isLinux || Platform.isAndroid) + ? "so" + : Platform.isMacOS + ? "dylib" + : Platform.isWindows + ? "dll" + : throw UnsupportedError("Unsupported Platform")}", + ), + ), ); print("Done!"); }