├── .github └── workflows │ ├── ci.yml │ ├── lints.yml │ └── release.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile.toml ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── network │ └── subsocial │ └── subsocial_sdk │ └── SubsocialSdkPlugin.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── network │ │ │ │ │ └── subsocial │ │ │ │ │ └── subsocial_sdk_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── init.py ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── SubsocialSdkPlugin.h │ ├── SubsocialSdkPlugin.m │ └── SwiftSubsocialSdkPlugin.swift └── subsocial_sdk.podspec ├── lib ├── allo_isolate.dart ├── ffi.dart ├── ffi_ext.dart ├── generated │ ├── def.pb.dart │ ├── def.pbenum.dart │ ├── def.pbjson.dart │ └── def.pbserver.dart ├── ipfs.dart ├── json_models.dart ├── multi_query.dart ├── subsocial_sdk.dart └── utils.dart ├── native ├── ffi │ ├── Cargo.toml │ ├── binding.h │ ├── build.rs │ ├── def.proto │ └── src │ │ ├── dart_utils.rs │ │ ├── handler.rs │ │ ├── lib.rs │ │ ├── pb │ │ ├── mod.rs │ │ ├── subsocial.rs │ │ └── subsoical.rs │ │ └── transformer.rs └── sdk │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── pallet │ ├── mod.rs │ ├── permissions.rs │ ├── posts.rs │ ├── profile_follows.rs │ ├── profiles.rs │ ├── reactions.rs │ ├── space_follows.rs │ ├── spaces.rs │ ├── traits.rs │ └── utils.rs │ └── runtime.rs ├── pubspec.lock ├── pubspec.yaml ├── rust-toolchain ├── rustfmt.toml ├── scripts ├── build-release.sh └── protogen.sh └── test ├── ipfs_client_test.dart ├── json_models_test.dart └── subsocial_sdk_test.dart /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | ci: 7 | name: Compile and Test 8 | runs-on: macos-latest 9 | steps: 10 | - name: Cache LLVM and Clang 11 | id: cache-llvm 12 | uses: actions/cache@v2 13 | with: 14 | path: ${{ runner.temp }}/llvm/10.0 15 | key: ${{ runner.os }}-cached-llvm-10.0 16 | 17 | - name: Install LLVM and Clang 18 | uses: KyleMayes/install-llvm-action@v1 19 | with: 20 | version: '10.0' 21 | directory: ${{ runner.temp }}/llvm/10.0 22 | cached: ${{ steps.cache-llvm.outputs.cache-hit }} 23 | 24 | - name: Checkout sources 25 | uses: actions/checkout@v2 26 | 27 | - name: Install Rust Toolchain 28 | id: rust_toolchain 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: nightly 33 | override: false 34 | 35 | - name: Cache Cargo 36 | uses: actions/cache@v2 37 | with: 38 | path: | 39 | ~/.cargo/registry 40 | ~/.cargo/git 41 | ~/.cargo/bin 42 | target 43 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ steps.rust_toolchain.outputs.rustc_hash }} 44 | 45 | - name: Install Cargo Tools (cargo-make) 46 | uses: davidB/rust-cargo-make@v1 47 | 48 | - name: Set up JDK 1.8 49 | uses: actions/setup-java@v1 50 | with: 51 | java-version: 1.8 52 | 53 | - name: Setup Android SDK 54 | uses: android-actions/setup-android@v2 55 | 56 | - name: Setup Android NDK 57 | uses: nttld/setup-ndk@v1 58 | with: 59 | ndk-version: r22b 60 | 61 | - name: Setup Flutter 62 | uses: subosito/flutter-action@v1 63 | with: 64 | channel: 'stable' 65 | 66 | - name: Cache Flutter 67 | uses: actions/cache@v2 68 | with: 69 | path: | 70 | ~/.pub-cache 71 | build 72 | key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }} 73 | 74 | - name: Run Flutter pub get 75 | run: flutter pub get 76 | 77 | - name: Install Android Targets 78 | run: rustup target add aarch64-linux-android x86_64-linux-android 79 | 80 | - name: Install iOS Targets 81 | run: rustup target add aarch64-apple-ios x86_64-apple-ios 82 | 83 | - name: Run cargo make native (Debug) 84 | uses: actions-rs/cargo@v1 85 | continue-on-error: false 86 | with: 87 | command: make 88 | args: native 89 | 90 | - name: Run cargo make android-dev (Debug) 91 | uses: actions-rs/cargo@v1 92 | continue-on-error: false 93 | with: 94 | command: make 95 | args: android-dev 96 | 97 | - name: Run cargo make ios (Debug) 98 | uses: actions-rs/cargo@v1 99 | continue-on-error: false 100 | with: 101 | command: make 102 | args: ios 103 | 104 | - name: Run flutter test 105 | run: flutter test 106 | 107 | 108 | -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Lints 4 | 5 | jobs: 6 | combo: 7 | name: Clippy + rustfmt 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install nightly toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: false 19 | components: rustfmt, clippy 20 | 21 | - name: Cache Cargo 22 | uses: actions/cache@v2 23 | with: 24 | path: | 25 | ~/.cargo/registry 26 | ~/.cargo/git 27 | ~/.cargo/bin 28 | target 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 30 | 31 | - name: Run cargo fmt 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | 37 | - name: Run cargo clippy 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: clippy 41 | args: -- -D warnings 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: SDK Release 8 | 9 | jobs: 10 | release: 11 | name: Compile and Release SDK 12 | runs-on: macos-latest 13 | steps: 14 | - name: Cache LLVM and Clang 15 | id: cache-llvm 16 | uses: actions/cache@v2 17 | with: 18 | path: ${{ runner.temp }}/llvm/10.0 19 | key: ${{ runner.os }}-cached-llvm-10.0 20 | 21 | - name: Install LLVM and Clang 22 | uses: KyleMayes/install-llvm-action@v1 23 | with: 24 | version: '10.0' 25 | directory: ${{ runner.temp }}/llvm/10.0 26 | cached: ${{ steps.cache-llvm.outputs.cache-hit }} 27 | 28 | - name: Checkout sources 29 | uses: actions/checkout@v2 30 | 31 | - name: Install Rust Toolchain 32 | id: rust_toolchain 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: nightly 37 | override: false 38 | 39 | - name: Cache Cargo 40 | uses: actions/cache@v2 41 | with: 42 | path: | 43 | ~/.cargo/registry 44 | ~/.cargo/git 45 | ~/.cargo/bin 46 | target 47 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ steps.rust_toolchain.outputs.rustc_hash }}-release 48 | 49 | - name: Install Cargo Tools (cargo-make) 50 | uses: davidB/rust-cargo-make@v1 51 | 52 | - name: Set up JDK 1.8 53 | uses: actions/setup-java@v1 54 | with: 55 | java-version: 1.8 56 | 57 | - name: Setup Android SDK 58 | uses: android-actions/setup-android@v2 59 | 60 | - name: Setup Android NDK 61 | uses: nttld/setup-ndk@v1 62 | with: 63 | ndk-version: r22b 64 | 65 | - name: Setup Flutter 66 | uses: subosito/flutter-action@v1 67 | with: 68 | channel: 'stable' 69 | 70 | - name: Cache Flutter 71 | uses: actions/cache@v2 72 | with: 73 | path: | 74 | ~/.pub-cache 75 | build 76 | key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }} 77 | 78 | - name: Run Flutter pub get 79 | run: flutter pub get 80 | 81 | - name: Install Android Targets 82 | run: rustup target add aarch64-linux-android x86_64-linux-android 83 | 84 | - name: Install iOS Targets 85 | run: rustup target add aarch64-apple-ios x86_64-apple-ios 86 | 87 | - name: Run cargo make android-dev (Release) 88 | uses: actions-rs/cargo@v1 89 | continue-on-error: false 90 | with: 91 | command: make 92 | args: android-dev --profile release 93 | 94 | - name: Run cargo make android-arm (Release) 95 | uses: actions-rs/cargo@v1 96 | continue-on-error: false 97 | with: 98 | command: make 99 | args: android-arm --profile release 100 | 101 | - name: Run cargo make ios (Release) 102 | uses: actions-rs/cargo@v1 103 | continue-on-error: false 104 | with: 105 | command: make 106 | args: ios --profile release 107 | 108 | - name: Collect Artifacts 109 | run: | 110 | mkdir -p build 111 | cp target/aarch64-linux-android/release/libsubsocial.so build/libsubsocial-aarch64-linux-android.so 112 | cp target/x86_64-linux-android/release/libsubsocial.so build/libsubsocial-x86_64-linux-android.so 113 | cp target/universal/release/libsubsocial.a build/libsubsocial-universal-apple-ios.a 114 | cp native/ffi/binding.h build/bindings.h 115 | 116 | - name: Create release 117 | uses: softprops/action-gh-release@v1 118 | with: 119 | prerelease: false 120 | files: | 121 | build/libsubsocial-aarch64-linux-android.so 122 | build/libsubsocial-x86_64-linux-android.so 123 | build/libsubsocial-universal-apple-ios.a 124 | build/bindings.h 125 | env: 126 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | target/ 9 | *.so 10 | *.a 11 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: b1395592de68cc8ac4522094ae59956dd21a91db 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "native/*", 4 | ] 5 | 6 | [profile.release] 7 | lto = true 8 | panic = 'unwind' 9 | codegen-units = 1 10 | 11 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true 3 | CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["native/ffi"] 4 | ANDROID_PLATFORM_VERSION = "28" 5 | LIB_OUT_DIR = "debug" 6 | TARGET_OS = "unknown" 7 | DEV = true 8 | RELEASE = false 9 | 10 | [env.release] 11 | RELEASE = true 12 | DEV = false 13 | LIB_OUT_DIR = "release" 14 | 15 | [tasks.test-flow] 16 | disabled = true 17 | 18 | [tasks.android-dev] 19 | dependencies = [ 20 | "setup-crate-type-android", 21 | "pre-android", 22 | "android-x86_64", 23 | "android-x86_64-release", 24 | "post-android-x86_64", 25 | "post-android", 26 | ] 27 | 28 | [tasks.android-arm] 29 | dependencies = [ 30 | "setup-crate-type-android", 31 | "pre-android", 32 | "android-aarch64", 33 | "android-aarch64-release", 34 | "post-android-aarch64", 35 | "post-android", 36 | ] 37 | 38 | 39 | [tasks.build] 40 | dependencies = ["android"] 41 | 42 | [tasks.build.mac] 43 | dependencies = ["android", "ios"] 44 | 45 | [tasks.native] 46 | dependencies = [ 47 | "native-build", 48 | "native-release", 49 | "post-native", 50 | ] 51 | 52 | [tasks.ios] 53 | dependencies = ["ios-build", "ios-release", "post-ios"] 54 | 55 | [tasks.ios-build] 56 | condition = { platforms = ["mac"], env_true = ["DEV"] } 57 | command = "cargo" 58 | args = ["lipo"] 59 | dependencies = [ 60 | "setup-crate-type-ios", 61 | ] 62 | 63 | [tasks.ios-release] 64 | condition = { platforms = ["mac"], env_true = ["RELEASE"] } 65 | command = "cargo" 66 | args = ["lipo", "--release"] 67 | dependencies = [ 68 | "setup-crate-type-ios", 69 | ] 70 | 71 | [tasks.native-build] 72 | condition = { env_true = ["DEV"] } 73 | command = "cargo" 74 | args = ["build"] 75 | dependencies = [ 76 | "setup-crate-type-native", 77 | ] 78 | 79 | [tasks.native-release] 80 | condition = { env_true = ["RELEASE"] } 81 | command = "cargo" 82 | args = ["build", "--release"] 83 | dependencies = [ 84 | "setup-crate-type-native", 85 | ] 86 | 87 | [tasks.post-ios] 88 | script_runner = "@duckscript" 89 | condition = { platforms = ["mac"] } 90 | script = [ 91 | """ 92 | cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/universal/${LIB_OUT_DIR}/libsubsocial.a \ 93 | ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/ios/libsubsocial.a 94 | """, 95 | """ 96 | cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/native/${CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER}/binding.h \ 97 | ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/ios/Classes/binding.h 98 | """, 99 | ] 100 | dependencies = ["restore-crate-type"] 101 | 102 | [tasks.android] 103 | dependencies = [ 104 | "setup-crate-type-android", 105 | "pre-android", 106 | "android-aarch64", 107 | "android-aarch64-release", 108 | "post-android-aarch64", 109 | "android-armv7", 110 | "android-armv7-release", 111 | "post-android-armv7", 112 | "android-i686", 113 | "android-i686-release", 114 | "post-android-i686", 115 | "android-x86_64", 116 | "android-x86_64-release", 117 | "post-android-x86_64", 118 | "post-android" 119 | ] 120 | 121 | [tasks.android-build] 122 | private = true 123 | condition = { env_true = ["DEV"], env_set = ["ANDROID_BUILD_TARGET"] } 124 | command = "cargo" 125 | args = [ 126 | "ndk", 127 | "--platform", 128 | "${ANDROID_PLATFORM_VERSION}", 129 | "--target", 130 | "${ANDROID_BUILD_TARGET}", 131 | "build", 132 | ] 133 | 134 | [tasks.android-build-release] 135 | private = true 136 | condition = { env_true = ["RELEASE"], env_set = ["ANDROID_BUILD_TARGET"] } 137 | command = "cargo" 138 | args = [ 139 | "ndk", 140 | "--platform", 141 | "${ANDROID_PLATFORM_VERSION}", 142 | "--target", 143 | "${ANDROID_BUILD_TARGET}", 144 | "build", 145 | "--release" 146 | ] 147 | 148 | [tasks.android-aarch64] 149 | private = true 150 | condition = { env_true = ["DEV"] } 151 | env = { ANDROID_BUILD_TARGET = "arm64-v8a" } 152 | run_task = "android-build" 153 | 154 | [tasks.android-armv7] 155 | private = true 156 | condition = { env_true = ["DEV"] } 157 | env = { ANDROID_BUILD_TARGET = "armeabi-v7a" } 158 | run_task = "android-build" 159 | 160 | [tasks.android-i686] 161 | private = true 162 | condition = { env_true = ["DEV"] } 163 | env = { ANDROID_BUILD_TARGET = "x86" } 164 | run_task = "android-build" 165 | 166 | [tasks.android-x86_64] 167 | private = true 168 | condition = { env_true = ["DEV"] } 169 | env = { ANDROID_BUILD_TARGET = "x86_64" } 170 | run_task = "android-build" 171 | 172 | [tasks.android-aarch64-release] 173 | private = true 174 | condition = { env_true = ["RELEASE"] } 175 | env = { ANDROID_BUILD_TARGET = "arm64-v8a" } 176 | run_task = "android-build-release" 177 | 178 | [tasks.android-armv7-release] 179 | private = true 180 | condition = { env_true = ["RELEASE"] } 181 | env = { ANDROID_BUILD_TARGET = "armeabi-v7a" } 182 | run_task = "android-build-release" 183 | 184 | [tasks.android-i686-release] 185 | private = true 186 | condition = { env_true = ["RELEASE"] } 187 | env = { ANDROID_BUILD_TARGET = "x86" } 188 | run_task = "android-build-release" 189 | 190 | [tasks.android-x86_64-release] 191 | private = true 192 | condition = { env_true = ["RELEASE"] } 193 | env = { ANDROID_BUILD_TARGET = "x86_64" } 194 | run_task = "android-build-release" 195 | 196 | [tasks.pre-android] 197 | private = true 198 | script_runner = "@duckscript" 199 | script = [ 200 | "mkdir ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/android/src/main/jniLibs/arm64-v8a", 201 | "mkdir ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/android/src/main/jniLibs/armeabi-v7a", 202 | "mkdir ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/android/src/main/jniLibs/x86", 203 | "mkdir ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/android/src/main/jniLibs/x86_64", 204 | ] 205 | 206 | [tasks.copy-lib] 207 | private = true 208 | condition = { env_set = ["ANDROID_TARGET", "JNI_LIB_DIR"] } 209 | script_runner = "@duckscript" 210 | script = [ 211 | """ 212 | cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${ANDROID_TARGET}/${LIB_OUT_DIR}/libsubsocial.so \ 213 | ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/android/src/main/jniLibs/${JNI_LIB_DIR}/libsubsocial.so 214 | """, 215 | ] 216 | 217 | [tasks.post-android-aarch64] 218 | private = true 219 | env = { ANDROID_TARGET = "aarch64-linux-android", JNI_LIB_DIR = "arm64-v8a" } 220 | run_task = "copy-lib" 221 | 222 | [tasks.post-android-armv7] 223 | private = true 224 | env = { ANDROID_TARGET = "armv7-linux-androideabi", JNI_LIB_DIR = "armeabi-v7a" } 225 | run_task = "copy-lib" 226 | 227 | [tasks.post-android-i686] 228 | private = true 229 | env = { ANDROID_TARGET = "i686-linux-android", JNI_LIB_DIR = "x86" } 230 | run_task = "copy-lib" 231 | 232 | 233 | [tasks.post-android-x86_64] 234 | private = true 235 | env = { ANDROID_TARGET = "x86_64-linux-android", JNI_LIB_DIR = "x86_64" } 236 | run_task = "copy-lib" 237 | 238 | [tasks.post-native] 239 | dependencies = ["restore-crate-type"] 240 | 241 | [tasks.post-android] 242 | dependencies = ["restore-crate-type"] 243 | 244 | [tasks.setup-crate-type-native] 245 | private = true 246 | env = { TARGET_OS = "native" } 247 | run_task = "setup-crate-type" 248 | 249 | [tasks.setup-crate-type-android] 250 | private = true 251 | env = { TARGET_OS = "android" } 252 | run_task = "setup-crate-type" 253 | 254 | [tasks.setup-crate-type-ios] 255 | private = true 256 | env = { TARGET_OS = "ios" } 257 | run_task = "setup-crate-type" 258 | 259 | [tasks.setup-crate-type] 260 | private = true 261 | script_runner = "@duckscript" 262 | script = [ 263 | """ 264 | toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/native/${CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER}/Cargo.toml 265 | crate_type = set "" 266 | os = get_env TARGET_OS 267 | is_android = eq ${os} "android" 268 | is_ios = eq ${os} "ios" 269 | is_native = eq ${os} "native" 270 | if ${is_android} 271 | crate_type = set "cdylib" 272 | elseif ${is_ios} 273 | crate_type = set "staticlib" 274 | elseif ${is_native} 275 | crate_type = set "cdylib" 276 | else 277 | crate_type = set "rlib" 278 | end 279 | val = replace ${toml} "rlib" ${crate_type} 280 | result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/native/${CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER}/Cargo.toml ${val} 281 | assert ${result} 282 | """, 283 | ] 284 | 285 | [tasks.restore-crate-type] 286 | private = true 287 | script_runner = "@duckscript" 288 | script = [ 289 | """ 290 | toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/native/${CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER}/Cargo.toml 291 | crate_type = set "" 292 | os = get_env TARGET_OS 293 | is_android = eq ${os} "android" 294 | is_ios = eq ${os} "ios" 295 | is_native = eq ${os} "native" 296 | 297 | if ${is_android} 298 | crate_type = set "cdylib" 299 | elseif ${is_ios} 300 | crate_type = set "staticlib" 301 | elseif ${is_native} 302 | crate_type = set "cdylib" 303 | else 304 | crate_type = set "rlib" 305 | end 306 | val = replace ${toml} ${crate_type} "rlib" 307 | result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/native/${CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER}/Cargo.toml ${val} 308 | assert ${result} 309 | """, 310 | ] 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Subsocial Flutter SDK 2 | 3 | ## Usage 4 | 5 | in your flutter project run the following commands to add the SDK to your project as a package. 6 | 7 | ```bash 8 | git submodule add https://github.com/dappforce/subsocial-flutter.git packages/subsocial_sdk 9 | ``` 10 | 11 | then run the following command 12 | 13 | ```bash 14 | ./packages/subsocial_sdk/init.py 15 | ``` 16 | 17 | in your flutter project in `pubspec.yml` just add: 18 | 19 | ```yaml 20 | subsocial_sdk: 21 | path: packages/subsocial_sdk 22 | ``` 23 | 24 | then use the SDK as any other flutter package. 25 | 26 | **To Update the SDK** 27 | Simply run: 28 | 29 | ```bash 30 | git submodule foreach git pull origin main 31 | ``` 32 | 33 | then rerun the `init.py` script to fetch the latest native libs. 34 | 35 | ```bash 36 | ./packages/subsocial_sdk/init.py 37 | ``` 38 | 39 | ## To run the example app 40 | Run the following script 41 | 42 | ```bash 43 | ./init.py . 44 | ``` 45 | 46 | ## Development, Setup and, Tools 47 | 48 | > Note these instructions only for who are working on the development of the SDK 49 | > not the end-users that will use this SDK in the apps. 50 | 51 | * Cargo Plugins 52 | 53 | ```sh 54 | cargo install cargo-make 55 | ``` 56 | 57 | * Install LLVM (10+) in the following way: 58 | 59 | * **ubuntu/linux** 60 | 61 | 1. Install libclangdev - `sudo apt-get install libclang-dev`. 62 | 63 | * **Windows** 64 | 65 | 1. Install Visual Studio with C++ development support. 66 | 2. Install [LLVM](https://releases.llvm.org/download.html) 67 | or `winget install -e --id LLVM.LLVM`. 68 | 69 | * **MacOS** 70 | 71 | 1. Install Xcode. 72 | 2. Install LLVM - `brew install llvm`. 73 | 74 | ## Build and Test 75 | 76 | In the Root of the project simply run: 77 | 78 | ```sh 79 | cargo make native 80 | ``` 81 | 82 | To Run tests: 83 | 84 | ```sh 85 | flutter test 86 | ``` 87 | 88 | Then run the example flutter app: 89 | 90 | * Build the native libs 91 | 92 | ```sh 93 | cargo make android-dev # or ios 94 | ``` 95 | 96 | * then run the app (the example) 97 | 98 | ```sh 99 | flutter run 100 | ``` 101 | 102 | ## See also 103 | 104 | * [Dart Meets Rust: a match made in heaven ✨](https://dev.to/sunshine-chain/dart-meets-rust-a-match-made-in-heaven-9f5) 105 | * [Dart and Rust: the async story 🔃](https://dev.to/sunshine-chain/rust-and-dart-the-async-story-3adk) 106 | * [Flutterust](https://github.com/shekohex/flutterust) 107 | 108 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options_package.yaml 2 | exclude: 3 | - lib/generated/def.pb.dart 4 | - lib/generated/def.pbenum.dart 5 | - lib/generated/def.pbgrpc.dart 6 | - lib/generated/def.pbjson.dart 7 | 8 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'network.subsocial.subsocial_sdk' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.50' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 30 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 40 | } 41 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'subsocial_sdk' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/network/subsocial/subsocial_sdk/SubsocialSdkPlugin.kt: -------------------------------------------------------------------------------- 1 | package network.subsocial.subsocial_sdk 2 | 3 | import androidx.annotation.NonNull 4 | 5 | import io.flutter.embedding.engine.plugins.FlutterPlugin 6 | import io.flutter.plugin.common.MethodCall 7 | import io.flutter.plugin.common.MethodChannel 8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 9 | import io.flutter.plugin.common.MethodChannel.Result 10 | import io.flutter.plugin.common.PluginRegistry.Registrar 11 | 12 | /** SubsocialSdkPlugin */ 13 | class SubsocialSdkPlugin: FlutterPlugin, MethodCallHandler { 14 | /// The MethodChannel that will the communication between Flutter and native Android 15 | /// 16 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 17 | /// when the Flutter Engine is detached from the Activity 18 | private lateinit var channel : MethodChannel 19 | 20 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 21 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "subsocial_sdk") 22 | channel.setMethodCallHandler(this) 23 | } 24 | 25 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 26 | if (call.method == "getPlatformVersion") { 27 | result.success("Android ${android.os.Build.VERSION.RELEASE}") 28 | } else { 29 | result.notImplemented() 30 | } 31 | } 32 | 33 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 34 | channel.setMethodCallHandler(null) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: b1395592de68cc8ac4522094ae59956dd21a91db 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # subsocial_sdk_example 2 | 3 | Demonstrates how to use the subsocial_sdk plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "network.subsocial.subsocial_sdk_example" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/network/subsocial/subsocial_sdk_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package network.subsocial.subsocial_sdk_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | subsocial_sdk_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.1.1" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.6.1" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | clock: 40 | dependency: transitive 41 | description: 42 | name: clock 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.15.0" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.2" 60 | dio: 61 | dependency: transitive 62 | description: 63 | name: dio 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "4.0.0" 67 | dio_http2_adapter: 68 | dependency: transitive 69 | description: 70 | name: dio_http2_adapter 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.0.0" 74 | fake_async: 75 | dependency: transitive 76 | description: 77 | name: fake_async 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.2.0" 81 | ffi: 82 | dependency: transitive 83 | description: 84 | name: ffi 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.1.2" 88 | file: 89 | dependency: transitive 90 | description: 91 | name: file 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "6.1.2" 95 | fixnum: 96 | dependency: transitive 97 | description: 98 | name: fixnum 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.0.0" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_markdown: 108 | dependency: "direct main" 109 | description: 110 | name: flutter_markdown 111 | url: "https://pub.dartlang.org" 112 | source: hosted 113 | version: "0.6.2" 114 | flutter_test: 115 | dependency: "direct dev" 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | http2: 120 | dependency: transitive 121 | description: 122 | name: http2 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "2.0.0" 126 | http_parser: 127 | dependency: transitive 128 | description: 129 | name: http_parser 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "4.0.0" 133 | infinite_scroll_pagination: 134 | dependency: "direct main" 135 | description: 136 | name: infinite_scroll_pagination 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "3.1.0" 140 | markdown: 141 | dependency: transitive 142 | description: 143 | name: markdown 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "4.0.0" 147 | matcher: 148 | dependency: transitive 149 | description: 150 | name: matcher 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.12.10" 154 | meta: 155 | dependency: transitive 156 | description: 157 | name: meta 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "1.3.0" 161 | path: 162 | dependency: transitive 163 | description: 164 | name: path 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.8.0" 168 | path_provider: 169 | dependency: transitive 170 | description: 171 | name: path_provider 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "2.0.2" 175 | path_provider_linux: 176 | dependency: transitive 177 | description: 178 | name: path_provider_linux 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "2.0.0" 182 | path_provider_macos: 183 | dependency: transitive 184 | description: 185 | name: path_provider_macos 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "2.0.0" 189 | path_provider_platform_interface: 190 | dependency: transitive 191 | description: 192 | name: path_provider_platform_interface 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "2.0.1" 196 | path_provider_windows: 197 | dependency: transitive 198 | description: 199 | name: path_provider_windows 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "2.0.1" 203 | platform: 204 | dependency: transitive 205 | description: 206 | name: platform 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "3.0.0" 210 | plugin_platform_interface: 211 | dependency: transitive 212 | description: 213 | name: plugin_platform_interface 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "2.0.1" 217 | process: 218 | dependency: transitive 219 | description: 220 | name: process 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "4.2.1" 224 | protobuf: 225 | dependency: transitive 226 | description: 227 | name: protobuf 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "2.0.0" 231 | sky_engine: 232 | dependency: transitive 233 | description: flutter 234 | source: sdk 235 | version: "0.0.99" 236 | sliver_tools: 237 | dependency: transitive 238 | description: 239 | name: sliver_tools 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "0.2.5" 243 | source_span: 244 | dependency: transitive 245 | description: 246 | name: source_span 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "1.8.1" 250 | stack_trace: 251 | dependency: transitive 252 | description: 253 | name: stack_trace 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.10.0" 257 | stream_channel: 258 | dependency: transitive 259 | description: 260 | name: stream_channel 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "2.1.0" 264 | string_scanner: 265 | dependency: transitive 266 | description: 267 | name: string_scanner 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "1.1.0" 271 | subsocial_sdk: 272 | dependency: "direct main" 273 | description: 274 | path: ".." 275 | relative: true 276 | source: path 277 | version: "0.1.0" 278 | term_glyph: 279 | dependency: transitive 280 | description: 281 | name: term_glyph 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.2.0" 285 | test_api: 286 | dependency: transitive 287 | description: 288 | name: test_api 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "0.3.0" 292 | typed_data: 293 | dependency: transitive 294 | description: 295 | name: typed_data 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "1.3.0" 299 | vector_math: 300 | dependency: transitive 301 | description: 302 | name: vector_math 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "2.1.0" 306 | win32: 307 | dependency: transitive 308 | description: 309 | name: win32 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.2.5" 313 | xdg_directories: 314 | dependency: transitive 315 | description: 316 | name: xdg_directories 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.2.0" 320 | sdks: 321 | dart: ">=2.13.0 <3.0.0" 322 | flutter: ">=1.22.0" 323 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: subsocial_sdk_example 2 | description: Demonstrates how to use the subsocial_sdk plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | infinite_scroll_pagination: ^3.1.0 15 | subsocial_sdk: 16 | # When depending on this package from a real application you should use: 17 | # subsocial_sdk: ^x.y.z 18 | # See https://dart.dev/tools/pub/dependencies#version-constraints 19 | # The example app is bundled with the plugin so we use a path dependency on 20 | # the parent directory to use the current plugin's version. 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.2 26 | flutter_markdown: ^0.6.2 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | # For information on the generic Dart part of this file, see the 33 | # following page: https://dart.dev/tools/pub/pubspec 34 | 35 | # The following section is specific to Flutter. 36 | flutter: 37 | 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | 43 | # To add assets to your application, add an assets section, like this: 44 | # assets: 45 | # - images/a_dot_burr.jpeg 46 | # - images/a_dot_ham.jpeg 47 | 48 | # An image asset can refer to one or more resolution-specific "variants", see 49 | # https://flutter.dev/assets-and-images/#resolution-aware. 50 | 51 | # For details regarding adding assets from package dependencies, see 52 | # https://flutter.dev/assets-and-images/#from-packages 53 | 54 | # To add custom fonts to your application, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts from package dependencies, 72 | # see https://flutter.dev/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | -------------------------------------------------------------------------------- /init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | 7 | import requests 8 | 9 | # Script Configuration. 10 | github_user = "shekohex" 11 | github_repo = "subsocial-flutter" 12 | package_name = 'subsocial_sdk' 13 | lib_name = 'subsocial' 14 | 15 | # Script arguments. 16 | 17 | base_url = f'https://github.com/{github_user}/{github_repo}/releases/latest/download' 18 | base_dir = os.path.join(os.getcwd(), 'packages', package_name) \ 19 | if len(sys.argv) < 2 else os.path.normpath(sys.argv[1]) 20 | android_base = os.path.join(base_dir, 'android', 'src', 'main', 'jniLibs') 21 | ios_base = os.path.join(base_dir, 'ios') 22 | target_files: list = [ 23 | ( 24 | f'{base_url}/lib{lib_name}-aarch64-linux-android.so', 25 | os.path.join(android_base, 'arm64-v8a'), 26 | f'lib{lib_name}.so', 27 | 'arm64 Devices (release build) for real android devices', 28 | ), 29 | ( 30 | f'{base_url}/lib{lib_name}-x86_64-linux-android.so', 31 | os.path.join(android_base, 'x86_64'), 32 | f'lib{lib_name}.so', 33 | 'x86_64 Devices (release build) for emulator during development', 34 | ), 35 | ( 36 | f'{base_url}/lib{lib_name}-universal-apple-ios.a', 37 | ios_base, 38 | f'lib{lib_name}.a', 39 | 'Universal Apple IOS (release build) for both real and simulated devices', 40 | ), 41 | ( 42 | f'{base_url}/bindings.h', 43 | os.path.join(ios_base, 'Classes'), 44 | 'bindings.h', 45 | 'C Header file bindings between Rust and Dart', 46 | ), 47 | ] 48 | 49 | # Helper functions 50 | 51 | 52 | def download(url: str, dest: str, filename: str): 53 | if not os.path.exists(dest): 54 | os.makedirs(dest, exist_ok=True) # create folder if it does not exist 55 | 56 | file_path = os.path.join(dest, filename) 57 | 58 | r = requests.get(url, stream=True, allow_redirects=True) 59 | if r.ok: 60 | with open(file_path, 'wb') as f: 61 | for chunk in r.iter_content(chunk_size=1024 * 8): 62 | if chunk: 63 | f.write(chunk) 64 | f.flush() 65 | os.fsync(f.fileno()) 66 | else: # HTTP status code 4XX/5XX 67 | print(f'Download failed: status code {r.status_code}\n{r.text}') 68 | print(f'failed to download {url}') 69 | print('Please rerun the script to try again.\n') 70 | 71 | # Init script code. 72 | 73 | 74 | print(f'Starting downloading Native libraries for {package_name} ..') 75 | print('Please wait this could take some time, depending on your internet connection.') 76 | print() 77 | 78 | for (url, dest, filename, msg) in target_files: 79 | print(f'Starting to download {filename} [{msg}]\nDestination: {dest}') 80 | download(url, dest, filename) 81 | 82 | print('Finished downloading files.') 83 | print() 84 | subprocess.call(['flutter', 'pub', 'get']) 85 | print('Done.') 86 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappforce/subsocial-flutter/3f3f55d649682552b87c87324d74119b9cba5f62/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/SubsocialSdkPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "./bindings.h" 3 | 4 | @interface SubsocialSdkPlugin : NSObject 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Classes/SubsocialSdkPlugin.m: -------------------------------------------------------------------------------- 1 | #import "SubsocialSdkPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "subsocial_sdk-Swift.h" 9 | #endif 10 | 11 | @implementation SubsocialSdkPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftSubsocialSdkPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/SwiftSubsocialSdkPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftSubsocialSdkPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "subsocial_sdk", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftSubsocialSdkPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | 15 | public static func dummy() { 16 | subsocial_link_me_plz() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/subsocial_sdk.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint subsocial_sdk.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'subsocial_sdk' 7 | s.version = '0.0.1' 8 | s.summary = 'Subsocial Flutter SDK' 9 | s.description = <<-DESC 10 | Subsocial Flutter SDK. 11 | DESC 12 | s.homepage = 'http://subsocial.network' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Subsocial Network' => 'hello@subsocial.network' } 15 | s.source = { :path => '.' } 16 | s.public_header_files = 'Classes**/*.h' 17 | s.source_files = 'Classes/**/*' 18 | s.static_framework = true 19 | s.vendored_libraries = "libsubsocial.a" 20 | s.dependency 'Flutter' 21 | s.platform = :ios, '8.0' 22 | 23 | # Flutter.framework does not contain a i386 slice. 24 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 25 | s.swift_version = '5.0' 26 | end 27 | -------------------------------------------------------------------------------- /lib/allo_isolate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | 3 | // ignore: avoid_private_typedef_functions, camel_case_types 4 | typedef _store_dart_post_cobject_C = Void Function( 5 | Pointer)>> ptr, 6 | ); 7 | 8 | // ignore: avoid_private_typedef_functions, camel_case_types 9 | typedef _store_dart_post_cobject_Dart = void Function( 10 | Pointer)>> ptr, 11 | ); 12 | 13 | /// `allo-isolate` Rust crate bindings. 14 | class AlloIsolate { 15 | late final DynamicLibrary lib; 16 | // ignore: non_constant_identifier_names 17 | _store_dart_post_cobject_Dart? _store_dart_post_cobject; 18 | 19 | AlloIsolate({required this.lib}); 20 | 21 | // ignore: non_constant_identifier_names 22 | void hook() { 23 | _store_dart_post_cobject ??= lib.lookupFunction<_store_dart_post_cobject_C, 24 | _store_dart_post_cobject_Dart>('store_dart_post_cobject'); 25 | _store_dart_post_cobject?.call(NativeApi.postCObject); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/ffi.dart: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED FILE, DO NOT EDIT. 2 | // 3 | // Generated by `package:ffigen`. 4 | import 'dart:ffi' as ffi; 5 | 6 | /// Subscoial FFI Binding 7 | class RawSubsoical { 8 | /// Holds the symbol lookup function. 9 | final ffi.Pointer Function(String symbolName) 10 | _lookup; 11 | 12 | /// The symbols are looked up in [dynamicLibrary]. 13 | RawSubsoical(ffi.DynamicLibrary dynamicLibrary) 14 | : _lookup = dynamicLibrary.lookup; 15 | 16 | /// The symbols are looked up with [lookup]. 17 | RawSubsoical.fromLookup( 18 | ffi.Pointer Function(String symbolName) 19 | lookup) 20 | : _lookup = lookup; 21 | 22 | int subsocial_dispatch( 23 | int port, 24 | ffi.Pointer buffer, 25 | ) { 26 | return _subsocial_dispatch( 27 | port, 28 | buffer, 29 | ); 30 | } 31 | 32 | late final _subsocial_dispatch_ptr = 33 | _lookup>('subsocial_dispatch'); 34 | late final _dart_subsocial_dispatch _subsocial_dispatch = 35 | _subsocial_dispatch_ptr.asFunction<_dart_subsocial_dispatch>(); 36 | 37 | int subsocial_init_client( 38 | int port, 39 | ffi.Pointer config, 40 | ) { 41 | return _subsocial_init_client( 42 | port, 43 | config, 44 | ); 45 | } 46 | 47 | late final _subsocial_init_client_ptr = 48 | _lookup>( 49 | 'subsocial_init_client'); 50 | late final _dart_subsocial_init_client _subsocial_init_client = 51 | _subsocial_init_client_ptr.asFunction<_dart_subsocial_init_client>(); 52 | 53 | /// a no-op function that forces xcode to link to our lib. 54 | /// ## Safety 55 | /// lol 56 | void subsocial_link_me_plz() { 57 | return _subsocial_link_me_plz(); 58 | } 59 | 60 | late final _subsocial_link_me_plz_ptr = 61 | _lookup>( 62 | 'subsocial_link_me_plz'); 63 | late final _dart_subsocial_link_me_plz _subsocial_link_me_plz = 64 | _subsocial_link_me_plz_ptr.asFunction<_dart_subsocial_link_me_plz>(); 65 | } 66 | 67 | class SubscoialConfig extends ffi.Struct { 68 | external ffi.Pointer url; 69 | } 70 | 71 | /// Owned version of Dart's [Uint8List] in Rust. 72 | /// 73 | /// **Note**: Automatically frees the underlying memory allocated from Dart. 74 | class Uint8List extends ffi.Struct { 75 | external ffi.Pointer buf; 76 | 77 | @ffi.Uint64() 78 | external int len; 79 | } 80 | 81 | typedef _c_subsocial_dispatch = ffi.Int32 Function( 82 | ffi.Int64 port, 83 | ffi.Pointer buffer, 84 | ); 85 | 86 | typedef _dart_subsocial_dispatch = int Function( 87 | int port, 88 | ffi.Pointer buffer, 89 | ); 90 | 91 | typedef _c_subsocial_init_client = ffi.Int32 Function( 92 | ffi.Int64 port, 93 | ffi.Pointer config, 94 | ); 95 | 96 | typedef _dart_subsocial_init_client = int Function( 97 | int port, 98 | ffi.Pointer config, 99 | ); 100 | 101 | typedef _c_subsocial_link_me_plz = ffi.Void Function(); 102 | 103 | typedef _dart_subsocial_link_me_plz = void Function(); 104 | -------------------------------------------------------------------------------- /lib/ffi_ext.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:ffi/ffi.dart' as ffi; 5 | 6 | import 'ffi.dart' as ffi; 7 | 8 | extension Uint8ListAsPtr on Uint8List { 9 | Pointer asPtr() { 10 | final ptr = ffi.malloc.allocate(length) 11 | ..asTypedList(length).setAll(0, this); 12 | 13 | final buf = ffi.malloc(); 14 | buf.ref 15 | ..buf = ptr 16 | ..len = length; 17 | return buf; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/generated/def.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: def.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | // ignore_for_file: UNDEFINED_SHOWN_NAME 9 | import 'dart:core' as $core; 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class Error_Kind extends $pb.ProtobufEnum { 13 | static const Error_Kind KIND_UNKNOWN = Error_Kind._( 14 | 0, 15 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 16 | ? '' 17 | : 'KIND_UNKNOWN'); 18 | static const Error_Kind KIND_NETWORK = Error_Kind._( 19 | 1, 20 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 21 | ? '' 22 | : 'KIND_NETWORK'); 23 | static const Error_Kind KIND_INVALID_PROTO = Error_Kind._( 24 | 2, 25 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 26 | ? '' 27 | : 'KIND_INVALID_PROTO'); 28 | static const Error_Kind KIND_INVALID_REQUEST = Error_Kind._( 29 | 3, 30 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 31 | ? '' 32 | : 'KIND_INVALID_REQUEST'); 33 | static const Error_Kind KIND_NOT_FOUND = Error_Kind._( 34 | 4, 35 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 36 | ? '' 37 | : 'KIND_NOT_FOUND'); 38 | static const Error_Kind KIND_SUBXT = Error_Kind._( 39 | 5, 40 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 41 | ? '' 42 | : 'KIND_SUBXT'); 43 | 44 | static const $core.List values = [ 45 | KIND_UNKNOWN, 46 | KIND_NETWORK, 47 | KIND_INVALID_PROTO, 48 | KIND_INVALID_REQUEST, 49 | KIND_NOT_FOUND, 50 | KIND_SUBXT, 51 | ]; 52 | 53 | static final $core.Map<$core.int, Error_Kind> _byValue = 54 | $pb.ProtobufEnum.initByValue(values); 55 | static Error_Kind? valueOf($core.int value) => _byValue[value]; 56 | 57 | const Error_Kind._($core.int v, $core.String n) : super(v, n); 58 | } 59 | 60 | class Reaction_ReactionKind extends $pb.ProtobufEnum { 61 | static const Reaction_ReactionKind UNKNOWN = Reaction_ReactionKind._( 62 | 0, 63 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 64 | ? '' 65 | : 'UNKNOWN'); 66 | static const Reaction_ReactionKind UP_VOTE = Reaction_ReactionKind._( 67 | 1, 68 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 69 | ? '' 70 | : 'UP_VOTE'); 71 | static const Reaction_ReactionKind DOWN_VOTE = Reaction_ReactionKind._( 72 | 2, 73 | const $core.bool.fromEnvironment('protobuf.omit_enum_names') 74 | ? '' 75 | : 'DOWN_VOTE'); 76 | 77 | static const $core.List values = 78 | [ 79 | UNKNOWN, 80 | UP_VOTE, 81 | DOWN_VOTE, 82 | ]; 83 | 84 | static final $core.Map<$core.int, Reaction_ReactionKind> _byValue = 85 | $pb.ProtobufEnum.initByValue(values); 86 | static Reaction_ReactionKind? valueOf($core.int value) => _byValue[value]; 87 | 88 | const Reaction_ReactionKind._($core.int v, $core.String n) : super(v, n); 89 | } 90 | -------------------------------------------------------------------------------- /lib/generated/def.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: def.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'def.pb.dart'; 9 | -------------------------------------------------------------------------------- /lib/ipfs.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_http2_adapter/dio_http2_adapter.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | 8 | import 'package:path/path.dart' as p; 9 | 10 | const String _kIpfsMediaBaseUrl = 'https://app.subsocial.network/ipfs/ipfs'; 11 | const String _kIpfsBaseUrl = 12 | 'https://app.subsocial.network/offchain/v1/ipfs/get'; 13 | 14 | class IpfsClient { 15 | static final Dio _client = Dio() 16 | ..httpClientAdapter = Http2Adapter( 17 | ConnectionManager( 18 | idleTimeout: 10 * 1000, 19 | ), 20 | ); 21 | 22 | String mediaUrl(String cid) => '$_kIpfsMediaBaseUrl/$cid'; 23 | 24 | Future media(String cid) async { 25 | final tmpDir = await getTemporaryDirectory(); 26 | final savingPath = p.join(tmpDir.path, cid); 27 | final result = await _client.download( 28 | mediaUrl(cid), 29 | savingPath, 30 | ); 31 | if (result.statusCode == 200) { 32 | final f = File(savingPath); 33 | final bytes = await f.readAsBytes(); 34 | await f.delete(); // delete the tmp file. 35 | return bytes; 36 | } else { 37 | return null; 38 | } 39 | } 40 | 41 | Future> query( 42 | List cids, 43 | T Function(Map) converter, 44 | ) async { 45 | final result = await _client.post>( 46 | _kIpfsBaseUrl, 47 | data: { 48 | 'cids': cids, 49 | }, 50 | options: Options( 51 | contentType: ContentType.json.value, 52 | ), 53 | ); 54 | if (result.statusCode == 200) { 55 | return result.data!.map( 56 | (key, value) => MapEntry(key, converter(value as Map)), 57 | ); 58 | } else { 59 | return {}; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/json_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:subsocial_sdk/subsocial_sdk.dart'; 3 | 4 | class PostMetadata { 5 | late String body; 6 | late List tags; 7 | late String? title; 8 | late String? image; 9 | String? link; 10 | String? canonical; 11 | 12 | PostMetadata({ 13 | required this.body, 14 | required this.tags, 15 | this.title, 16 | this.image, 17 | this.link, 18 | this.canonical, 19 | }); 20 | 21 | PostMetadata.fromJson(Map json) { 22 | body = json['body'] as String; 23 | if (json['tags'] != null) { 24 | tags = (json['tags'] as List).cast(); 25 | } else { 26 | tags = []; 27 | } 28 | image = json['image'] as String?; 29 | title = json['title'] as String?; 30 | link = json['link'] as String?; 31 | canonical = json['canonical'] as String?; 32 | } 33 | } 34 | 35 | @immutable 36 | class PostWithMetadata { 37 | final Post post; 38 | final PostMetadata metadata; 39 | const PostWithMetadata({required this.post, required this.metadata}); 40 | } 41 | 42 | class SocialAccountMetadata { 43 | late String? about; 44 | late String? name; 45 | late String? avatar; 46 | 47 | SocialAccountMetadata({ 48 | this.name, 49 | this.about, 50 | this.avatar, 51 | }); 52 | 53 | SocialAccountMetadata.fromJson(Map json) { 54 | name = json['name'] as String?; 55 | about = json['about'] as String?; 56 | avatar = json['avatar'] as String?; 57 | } 58 | } 59 | 60 | @immutable 61 | class SocialAccountWithMetadata { 62 | final SocialAccount socialAccount; 63 | final SocialAccountMetadata metadata; 64 | const SocialAccountWithMetadata({ 65 | required this.socialAccount, 66 | required this.metadata, 67 | }); 68 | } 69 | 70 | class SpaceMetadata { 71 | late List links; 72 | late String name; 73 | late List tags; 74 | String? handle; 75 | String? about; 76 | String? image; 77 | 78 | SpaceMetadata({ 79 | required this.name, 80 | this.links = const [], 81 | this.tags = const [], 82 | this.handle, 83 | this.about, 84 | this.image, 85 | }); 86 | 87 | SpaceMetadata.fromJson(Map json) { 88 | about = json['about'] as String?; 89 | handle = json['handle'] as String?; 90 | image = json['image'] as String?; 91 | if (json['links'] != null) { 92 | links = (json['links'] as List).cast(); 93 | } else { 94 | links = []; 95 | } 96 | name = json['name'] as String; 97 | tags = (json['tags'] as List).cast(); 98 | } 99 | } 100 | 101 | @immutable 102 | class SpaceWithMetadata { 103 | final Space space; 104 | final SpaceMetadata metadata; 105 | const SpaceWithMetadata({required this.space, required this.metadata}); 106 | } 107 | -------------------------------------------------------------------------------- /lib/multi_query.dart: -------------------------------------------------------------------------------- 1 | import 'package:subsocial_sdk/subsocial_sdk.dart'; 2 | 3 | /// The four visible state filters correspond to the next conditions: 4 | /// 5 | /// - `onlyVisible` – The `hidden` field on corresponding Substrate struct (e.g. `Space` or `Post`) is `false`. 6 | /// - `onlyHidden` – The `hidden` field on corresponding Substrate struct (e.g. `Space` or `Post`) is `true`. 7 | /// - `onlyPublic` – The `hidden` field on corresponding Substrate struct (e.g. `Space` or `Post`) is `false` 8 | /// and there is a corresponding JSON file on IPFS. 9 | /// - `onlyUnlisted` – Either the `hidden` field on corresponding Substrate struct (e.g. `Space` or `Post`) is `true` 10 | /// or there is a no corresponding JSON file on IPFS. 11 | enum Visibility { 12 | onlyVisible, 13 | onlyHidden, 14 | onlyPublic, 15 | onlyUnlisted, 16 | } 17 | 18 | class Filter { 19 | final Visibility visibility; 20 | final bool withContentOnly; 21 | final List ids; 22 | 23 | const Filter({ 24 | required this.ids, 25 | this.visibility = Visibility.onlyVisible, 26 | this.withContentOnly = false, 27 | }); 28 | } 29 | 30 | extension MultiQuery on Subsocial { 31 | /// Returns a stream of all public [Space]s. 32 | Stream publicSpaces({ 33 | int page = 1, 34 | int limit = 30, 35 | }) async* { 36 | final lastSpaceId = await nextSpaceId() - 1; 37 | final from = lastSpaceId - ((page - 1) * limit); 38 | final to = from - limit; 39 | final ipfs = IpfsClient(); 40 | for (int i = from; i >= to; i--) { 41 | try { 42 | final space = await spaceById(i); 43 | if (space.hidden) continue; // skip hidden space 44 | final cid = space.content.ipfs; 45 | if (cid.isEmpty) continue; // no content; 46 | final metadataMap = await ipfs.query( 47 | [cid], 48 | (json) => SpaceMetadata.fromJson(json), 49 | ); 50 | final maybeMetadata = metadataMap[cid]; 51 | if (maybeMetadata == null) continue; // skip this too. 52 | yield SpaceWithMetadata(space: space, metadata: maybeMetadata); 53 | } catch (_) { 54 | continue; 55 | } 56 | } 57 | } 58 | 59 | /// Returns a stream of all public [Post]s. 60 | Stream publicPosts({ 61 | int page = 1, 62 | int limit = 30, 63 | }) async* { 64 | final lastPostId = await nextPostId() - 1; 65 | final from = lastPostId - ((page - 1) * limit); 66 | final to = from - limit; 67 | final ipfs = IpfsClient(); 68 | for (int i = from; i >= to; i--) { 69 | try { 70 | final post = await postById(i); 71 | if (post.hidden) continue; // skip hidden space 72 | final cid = post.content.ipfs; 73 | if (cid.isEmpty) continue; // no content; 74 | final metadataMap = await ipfs.query( 75 | [cid], 76 | (json) => PostMetadata.fromJson(json), 77 | ); 78 | final maybeMetadata = metadataMap[cid]; 79 | if (maybeMetadata == null) continue; // skip this too. 80 | yield PostWithMetadata(post: post, metadata: maybeMetadata); 81 | } catch (_) { 82 | continue; 83 | } 84 | } 85 | } 86 | 87 | Future> spaces(Filter filter) async { 88 | final List spaces = []; 89 | for (final id in filter.ids) { 90 | try { 91 | final space = await spaceById(id); 92 | switch (filter.visibility) { 93 | case Visibility.onlyUnlisted: 94 | if (space.hidden && !space.hasContent()) spaces.add(space); 95 | break; 96 | case Visibility.onlyHidden: 97 | if (space.hidden) spaces.add(space); 98 | break; 99 | case Visibility.onlyVisible: 100 | if (!space.hidden) spaces.add(space); 101 | break; 102 | case Visibility.onlyPublic: 103 | if (!space.hidden && space.hasContent()) spaces.add(space); 104 | break; 105 | } 106 | } catch (_) { 107 | continue; 108 | } 109 | } 110 | return spaces; 111 | } 112 | 113 | Future> posts(Filter filter) async { 114 | final List posts = []; 115 | for (final id in filter.ids) { 116 | try { 117 | final post = await postById(id); 118 | switch (filter.visibility) { 119 | case Visibility.onlyUnlisted: 120 | if (post.hidden && !post.hasContent()) posts.add(post); 121 | break; 122 | case Visibility.onlyHidden: 123 | if (post.hidden) posts.add(post); 124 | break; 125 | case Visibility.onlyVisible: 126 | if (!post.hidden) posts.add(post); 127 | break; 128 | case Visibility.onlyPublic: 129 | if (!post.hidden && post.hasContent()) posts.add(post); 130 | break; 131 | } 132 | } catch (_) { 133 | continue; 134 | } 135 | } 136 | return posts; 137 | } 138 | 139 | Future> socialAccounts(List ids) async { 140 | final List accounts = []; 141 | for (final id in ids) { 142 | try { 143 | final account = await socialAccountByAccountId(id); 144 | accounts.add(account); 145 | } catch (_) { 146 | continue; 147 | } 148 | } 149 | return accounts; 150 | } 151 | 152 | Future> reactions(List ids) async { 153 | final List reactions = []; 154 | for (final id in ids) { 155 | try { 156 | final reaction = await reactionById(id); 157 | reactions.add(reaction); 158 | } catch (_) { 159 | continue; 160 | } 161 | } 162 | return reactions; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/subsocial_sdk.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ffi'; 3 | import 'dart:io'; 4 | import 'dart:typed_data' as typed_data; 5 | 6 | import 'package:ffi/ffi.dart'; 7 | import 'package:protobuf/protobuf.dart'; 8 | import 'package:subsocial_sdk/allo_isolate.dart'; 9 | import 'package:subsocial_sdk/ffi.dart'; 10 | import 'package:subsocial_sdk/ffi_ext.dart'; 11 | import 'package:subsocial_sdk/generated/def.pb.dart'; 12 | import 'package:subsocial_sdk/utils.dart'; 13 | 14 | export 'generated/def.pb.dart'; 15 | export 'generated/def.pbenum.dart'; 16 | export 'ipfs.dart'; 17 | export 'json_models.dart'; 18 | export 'multi_query.dart'; 19 | 20 | typedef PostId = int; 21 | typedef SpaceId = int; 22 | typedef ReactionId = int; 23 | typedef AccountId = String; 24 | 25 | class Subsocial { 26 | static Subsocial? _instance; 27 | late final RawSubsoical _raw; 28 | Subsocial._(this._raw); 29 | static Future get instance async { 30 | if (_instance == null) { 31 | final dl = _load(); 32 | final raw = RawSubsoical(dl); 33 | AlloIsolate(lib: dl).hook(); 34 | final completer = Completer(); 35 | final port = singleCompletePort(completer); 36 | final config = malloc.call() 37 | ..ref.url = "wss://rpc.subsocial.network".toNativeUtf8().cast(); 38 | final result = raw.subsocial_init_client( 39 | port.nativePort, 40 | config, 41 | ); 42 | _assertOk(result); 43 | final res = await completer.future; 44 | if (res is String) { 45 | throw SubxtException(res); 46 | } 47 | _instance = Subsocial._(raw); 48 | return _instance!; 49 | } else { 50 | return _instance!; 51 | } 52 | } 53 | 54 | Future spaceById(SpaceId id) async { 55 | final req = Request( 56 | spaceById: GetSpaceById(spaceId: makeLongInt(id)), 57 | ); 58 | final res = await _dispatch(req); 59 | final val = res.ensureSpaceById(); 60 | return val.space; 61 | } 62 | 63 | Future spaceByHandle(String handle) async { 64 | final req = Request( 65 | spaceByHandle: GetSpaceByHandle(handle: handle), 66 | ); 67 | final res = await _dispatch(req); 68 | final val = res.ensureSpaceByHandle(); 69 | return val.space; 70 | } 71 | 72 | Future> spaceIdsByOwner(AccountId accountId) async { 73 | final req = Request( 74 | spaceIdsByOwner: GetSpaceIdsByOwner( 75 | accountId: accountId, 76 | ), 77 | ); 78 | final res = await _dispatch(req); 79 | final val = res.ensureSpaceIdsByOwner(); 80 | return val.spaceIds.map((e) => e.toInt()).toList(); 81 | } 82 | 83 | Future postById(PostId id) async { 84 | final req = Request( 85 | postById: GetPostById(postId: makeLongInt(id)), 86 | ); 87 | final res = await _dispatch(req); 88 | final val = res.ensurePostById(); 89 | return val.post; 90 | } 91 | 92 | Future reactionById(ReactionId id) async { 93 | final req = Request( 94 | reactionById: GetReactionById(reactionId: makeLongInt(id)), 95 | ); 96 | final res = await _dispatch(req); 97 | final val = res.ensureReactionById(); 98 | return val.reaction; 99 | } 100 | 101 | Future> postsIdsBySpaceId(SpaceId id) async { 102 | final req = Request( 103 | postIdsBySpaceId: GetPostIdsBySpaceId(spaceId: makeLongInt(id)), 104 | ); 105 | final res = await _dispatch(req); 106 | final val = res.ensurePostIdsBySpaceId(); 107 | return val.postIds.map((e) => e.toInt()).toList(); 108 | } 109 | 110 | Future> reactionIdsByPostId(PostId id) async { 111 | final req = Request( 112 | reactionIdsByPostId: GetReactionIdsByPostId(postId: makeLongInt(id)), 113 | ); 114 | final res = await _dispatch(req); 115 | final val = res.ensureReactionIdsByPostId(); 116 | return val.reactionIds.map((e) => e.toInt()).toList(); 117 | } 118 | 119 | Future> replyIdsByPostId(PostId id) async { 120 | final req = Request( 121 | replyIdsByPostId: GetReplyIdsByPostId(postId: makeLongInt(id)), 122 | ); 123 | final res = await _dispatch(req); 124 | final val = res.ensureReplyIdsByPostId(); 125 | return val.replyIds.map((e) => e.toInt()).toList(); 126 | } 127 | 128 | Future socialAccountByAccountId(AccountId accountId) async { 129 | final req = Request( 130 | socialAccountByAccountId: GetSocialAccountByAccountId( 131 | accountId: accountId, 132 | ), 133 | ); 134 | final res = await _dispatch(req); 135 | final val = res.ensureSocialAccountByAccountId(); 136 | return val.socialAccount; 137 | } 138 | 139 | Future nextSpaceId() async { 140 | final req = Request(nextSpaceId: GetNextSpaceId()); 141 | final res = await _dispatch(req); 142 | final val = res.ensureNextSpaceId(); 143 | return val.id.toInt(); 144 | } 145 | 146 | Future nextPostId() async { 147 | final req = Request(nextPostId: GetNextPostId()); 148 | final res = await _dispatch(req); 149 | final val = res.ensureNextPostId(); 150 | return val.id.toInt(); 151 | } 152 | 153 | Future> spaceFollowers(SpaceId spaceId) async { 154 | final req = Request( 155 | spaceFollowers: GetSpaceFollowers(spaceId: makeLongInt(spaceId)), 156 | ); 157 | final res = await _dispatch(req); 158 | final val = res.ensureSpaceFollowers(); 159 | return val.accountIds; 160 | } 161 | 162 | Future> spacesFollowedByAccount(AccountId accountId) async { 163 | final req = Request( 164 | spacesFollowedByAccount: GetSpacesFollowedByAccount( 165 | accountId: accountId, 166 | ), 167 | ); 168 | final res = await _dispatch(req); 169 | final val = res.ensureSpacesFollowedByAccount(); 170 | return val.spaceIds.map((e) => e.toInt()).toList(); 171 | } 172 | 173 | Future> accountFollowers(AccountId accountId) async { 174 | final req = Request( 175 | accountFollowers: GetAccountFollowers(accountId: accountId), 176 | ); 177 | final res = await _dispatch(req); 178 | final val = res.ensureAccountFollowers(); 179 | return val.accountIds; 180 | } 181 | 182 | Future> accountsFollowedByAccount(AccountId accountId) async { 183 | final req = Request( 184 | accountsFollowedByAccount: GetAccountsFollowedByAccount( 185 | accountId: accountId, 186 | ), 187 | ); 188 | final res = await _dispatch(req); 189 | final val = res.ensureAccountsFollowedByAccount(); 190 | return val.accountIds; 191 | } 192 | 193 | /// Generates new Account and sets this account as the default account 194 | /// for signing transactions. 195 | Future generateAccount({String? password}) async { 196 | final req = Request(generateAccount: GenerateAccount(password: password)); 197 | final res = await _dispatch(req); 198 | final val = res.ensureGeneratedAccount(); 199 | return val; 200 | } 201 | 202 | /// Imports already generated Account and sets this account as the default account 203 | /// for signing transactions, replacing any already accounts if there is any. 204 | /// 205 | /// Interprets the string `suri` in order to generate a key Pair. 206 | /// Returns both the pair and an optional seed, in the 207 | /// case that the pair can be expressed as a direct derivation from a seed (some cases, such as Sr25519 derivations 208 | /// with path components, cannot). 209 | /// 210 | /// This takes a helper function to do the key generation from a phrase, password and 211 | /// junction iterator. 212 | /// 213 | /// - If `suri` is a possibly `0x` prefixed 64-digit hex string, then it will be interpreted 214 | /// directly as a `MiniSecretKey` (aka "seed" in `subkey`). 215 | /// - If `suri` is a valid BIP-39 key phrase of 12, 15, 18, 21 or 24 words, then the key will 216 | /// be derived from it. In this case: 217 | /// - the phrase may be followed by one or more items delimited by `/` characters. 218 | /// - the path may be followed by `///`, in which case everything after the `///` is treated 219 | /// as a password. 220 | /// - If `suri` begins with a `/` character it is prefixed with the Substrate public `DEV_PHRASE` and 221 | /// interpreted as above. 222 | /// 223 | /// In this case they are interpreted as HDKD junctions; purely numeric items are interpreted as 224 | /// integers, non-numeric items as strings. Junctions prefixed with `/` are interpreted as soft 225 | /// junctions, and with `//` as hard junctions. 226 | /// 227 | /// There is no correspondence mapping between SURI strings and the keys they represent. 228 | /// Two different non-identical strings can actually lead to the same secret being derived. 229 | /// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros. 230 | /// Similarly an empty password (ending the SURI with `///`) is perfectly valid and will generally 231 | /// be equivalent to no password at all. 232 | /// 233 | /// `None` is returned if no matches are found. 234 | Future importAccount({ 235 | required String suri, 236 | String? password, 237 | }) async { 238 | final req = Request( 239 | importAccount: ImportAccount( 240 | password: password, 241 | suri: suri, 242 | ), 243 | ); 244 | final res = await _dispatch(req); 245 | final val = res.ensureImportedAccount(); 246 | return val; 247 | } 248 | 249 | void dispose() { 250 | // currently, there is nothing to dispose. 251 | } 252 | 253 | Future _dispatch(Request req) async { 254 | final completer = Completer(); 255 | final port = singleCompletePort(completer); 256 | final buffer = req.writeToBuffer().asPtr(); 257 | final result = _raw.subsocial_dispatch(port.nativePort, buffer); 258 | _assertOk(result); 259 | final resBytes = await completer.future; 260 | final res = Response.fromBuffer(resBytes); 261 | if (res.hasError()) { 262 | throw res.error; 263 | } 264 | return res; 265 | } 266 | } 267 | 268 | class BadProtoMessageException implements Exception {} 269 | 270 | class ClientNotInitializedException implements Exception {} 271 | 272 | class SubxtException implements Exception { 273 | final String message; 274 | const SubxtException(this.message); 275 | @override 276 | String toString() { 277 | return 'SubxtException($message)'; 278 | } 279 | } 280 | 281 | void _assertOk(int result) { 282 | if (result == 0xbadc0de) { 283 | throw BadProtoMessageException(); 284 | } else if (result == 0xdead) { 285 | throw ClientNotInitializedException(); 286 | } else { 287 | // all good 288 | } 289 | } 290 | 291 | /// Loads the Subsocial [DynamicLibrary] depending on the [Platform] 292 | DynamicLibrary _load() { 293 | if (Platform.isAndroid) { 294 | return DynamicLibrary.open('libsubsocial.so'); 295 | } else if (Platform.isIOS) { 296 | return DynamicLibrary.executable(); 297 | } else if (Platform.isLinux) { 298 | return DynamicLibrary.open('target/debug/libsubsocial.so'); 299 | } else if (Platform.isMacOS) { 300 | return DynamicLibrary.open('target/debug/libsubsocial.dylib'); 301 | } else { 302 | throw UnsupportedError('The Current Platform is not supported.'); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | /// original: https://github.com/dart-lang/isolate/blob/ca133acb5af3a60a026fa2aab12b81e60048b3be/lib/ports.dart 2 | 3 | import 'dart:async'; 4 | import 'dart:isolate'; 5 | 6 | /// Create a [SendPort] that accepts only one message. 7 | /// 8 | /// The [callback] function is called once, with the first message 9 | /// received by the receive port. 10 | /// All further messages are ignored. 11 | /// 12 | /// If [timeout] is supplied, it is used as a limit on how 13 | /// long it can take before the message is received. If a 14 | /// message isn't received in time, the `callback` function 15 | /// is called once with the [timeoutValue] instead. 16 | /// 17 | /// If the received value is not a [T], it will cause an uncaught 18 | /// asynchronous error in the current zone. 19 | /// 20 | /// Returns the `SendPort` expecting the single message. 21 | /// 22 | /// Equivalent to: 23 | /// ```dart 24 | /// (new ReceivePort() 25 | /// ..first.timeout(duration, () => timeoutValue).then(callback)) 26 | /// .sendPort 27 | /// ``` 28 | SendPort singleCallbackPort

(void Function(P? response) callback, 29 | {Duration? timeout, P? timeoutValue}) { 30 | final responsePort = RawReceivePort(); 31 | final zone = Zone.current; 32 | // ignore: parameter_assignments 33 | callback = zone.registerUnaryCallback(callback); 34 | Timer? timer; 35 | responsePort.handler = (response) { 36 | responsePort.close(); 37 | timer?.cancel(); 38 | zone.runUnary(callback, response as P); 39 | }; 40 | if (timeout != null) { 41 | timer = Timer(timeout, () { 42 | responsePort.close(); 43 | callback(timeoutValue); 44 | }); 45 | } 46 | return responsePort.sendPort; 47 | } 48 | 49 | /// Create a [SendPort] that accepts only one message. 50 | /// 51 | /// When the first message is received, the [callback] function is 52 | /// called with the message as argument, 53 | /// and the [completer] is completed with the result of that call. 54 | /// All further messages are ignored. 55 | /// 56 | /// If `callback` is omitted, it defaults to an identity function. 57 | /// The `callback` call may return a future, and the completer will 58 | /// wait for that future to complete. If [callback] is omitted, the 59 | /// message on the port must be an instance of [R]. 60 | /// 61 | /// If [timeout] is supplied, it is used as a limit on how 62 | /// long it can take before the message is received. If a 63 | /// message isn't received in time, the [onTimeout] is called, 64 | /// and `completer` is completed with the result of that call 65 | /// instead. 66 | /// The [callback] function will not be interrupted by the time-out, 67 | /// as long as the initial message is received in time. 68 | /// If `onTimeout` is omitted, it defaults to completing the `completer` with 69 | /// a [TimeoutException]. 70 | /// 71 | /// The [completer] may be a synchronous completer. It is only 72 | /// completed in response to another event, either a port message or a timer. 73 | /// 74 | /// Returns the `SendPort` expecting the single message. 75 | SendPort singleCompletePort(Completer completer, 76 | {FutureOr Function(P? message)? callback, 77 | Duration? timeout, 78 | FutureOr Function()? onTimeout}) { 79 | if (callback == null && timeout == null) { 80 | return singleCallbackPort((response) { 81 | _castComplete(completer, response); 82 | }); 83 | } 84 | final responsePort = RawReceivePort(); 85 | Timer? timer; 86 | if (callback == null) { 87 | responsePort.handler = (response) { 88 | responsePort.close(); 89 | timer?.cancel(); 90 | _castComplete(completer, response); 91 | }; 92 | } else { 93 | final zone = Zone.current; 94 | final action = zone.registerUnaryCallback((response) { 95 | try { 96 | // Also catch it if callback throws. 97 | completer.complete(callback(response as P)); 98 | } catch (error, stack) { 99 | completer.completeError(error, stack); 100 | } 101 | }); 102 | responsePort.handler = (response) { 103 | responsePort.close(); 104 | timer?.cancel(); 105 | zone.runUnary(action, response as P); 106 | }; 107 | } 108 | if (timeout != null) { 109 | timer = Timer(timeout, () { 110 | responsePort.close(); 111 | if (onTimeout != null) { 112 | completer.complete(Future.sync(onTimeout)); 113 | } else { 114 | completer 115 | .completeError(TimeoutException('Future not completed', timeout)); 116 | } 117 | }); 118 | } 119 | return responsePort.sendPort; 120 | } 121 | 122 | // Helper function that casts an object to a type and completes a 123 | // corresponding completer, or completes with the error if the cast fails. 124 | void _castComplete(Completer completer, Object? value) { 125 | try { 126 | completer.complete(value as R); 127 | } catch (error, stack) { 128 | completer.completeError(error, stack); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /native/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subsocial-ffi" 3 | version = "0.1.0" 4 | authors = ["Shady Khalifa "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "subsocial" 9 | # this value will change depending on the target os 10 | # for iOS it would be `staticlib` 11 | # for android it would be `c-dylib` 12 | crate-type = ["rlib"] 13 | 14 | [dependencies] 15 | sdk = { path = "../sdk", package = "subsocial-sdk" } 16 | bytes = "1.0" 17 | prost = "0.7" 18 | prost-types = "0.7" 19 | once_cell = "1.7" 20 | allo-isolate = { version = "0.1.8-beta", features = ["catch-unwind"] } 21 | # Runtime 22 | async-std = { version = "1.8", features = [] } 23 | 24 | [build-dependencies] 25 | cbindgen = "0.19" 26 | prost-build = "0.7" 27 | -------------------------------------------------------------------------------- /native/ffi/binding.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct SubscoialConfig { 4 | const char *url; 5 | }; 6 | 7 | // Owned version of Dart's [Uint8List] in Rust. 8 | // 9 | // **Note**: Automatically frees the underlying memory allocated from Dart. 10 | struct Uint8List { 11 | uint8_t *buf; 12 | uintptr_t len; 13 | }; 14 | 15 | int32_t subsocial_init_client(int64_t port, struct SubscoialConfig *config); 16 | 17 | int32_t subsocial_dispatch(int64_t port, struct Uint8List *buffer); 18 | 19 | // a no-op function that forces xcode to link to our lib. 20 | // ## Safety 21 | // lol 22 | void subsocial_link_me_plz(void); 23 | -------------------------------------------------------------------------------- /native/ffi/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | // Tell Cargo that if the given file changes, to rerun this build script. 5 | println!("cargo:rerun-if-changed=def.proto"); 6 | println!("cargo:rerun-if-changed=src/lib.rs"); 7 | 8 | prost_build::Config::new() 9 | .out_dir("src/pb") 10 | .compile_protos(&["def.proto"], &["."]) 11 | .expect("Failed to compile protobuf"); 12 | 13 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 14 | let config = cbindgen::Config { 15 | language: cbindgen::Language::C, 16 | documentation_style: cbindgen::DocumentationStyle::C99, 17 | line_length: 100, 18 | style: cbindgen::Style::Tag, 19 | no_includes: true, 20 | sys_includes: vec![String::from("stdint.h")], 21 | ..Default::default() 22 | }; 23 | cbindgen::Builder::new() 24 | .with_crate(crate_dir) 25 | .with_config(config) 26 | .generate() 27 | .expect("Unable to generate bindings") 28 | .write_to_file("binding.h"); 29 | } 30 | -------------------------------------------------------------------------------- /native/ffi/def.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package subsocial; 3 | 4 | 5 | message Request { 6 | oneof body { 7 | GetSpaceById space_by_id = 1; 8 | GetSpaceByHandle space_by_handle = 2; 9 | GetPostById post_by_id = 3; 10 | GetPostIdsBySpaceId post_ids_by_space_id = 4; 11 | GetReactionById reaction_by_id = 5; 12 | GetReactionIdsByPostId reaction_ids_by_post_id = 6; 13 | GetReplyIdsByPostId reply_ids_by_post_id = 7; 14 | GetSocialAccountByAccountId social_account_by_account_id = 8; 15 | GetNextSpaceId next_space_id = 9; 16 | GetNextPostId next_post_id = 10; 17 | GetSpaceIdsByOwner space_ids_by_owner = 11; 18 | GetSpaceFollowers space_followers = 12; 19 | GetSpacesFollowedByAccount spaces_followed_by_account = 13; 20 | GetAccountFollowers account_followers = 14; 21 | GetAccountsFollowedByAccount accounts_followed_by_account = 15; 22 | GenerateAccount generate_account = 16; 23 | ImportAccount import_account = 17; 24 | } 25 | } 26 | 27 | message Response { 28 | oneof body { 29 | Error error = 1; 30 | SpaceById space_by_id = 2; 31 | SpaceByHandle space_by_handle = 3; 32 | PostById post_by_id = 4; 33 | PostIdsBySpaceId post_ids_by_space_id = 5; 34 | ReactionById reaction_by_id = 6; 35 | ReactionIdsByPostId reaction_ids_by_post_id = 7; 36 | ReplyIdsByPostId reply_ids_by_post_id = 8; 37 | SocialAccountByAccountId social_account_by_account_id = 9; 38 | NextSpaceId next_space_id = 10; 39 | NextPostId next_post_id = 11; 40 | SpaceIdsByOwner space_ids_by_owner = 12; 41 | SpaceFollowers space_followers = 13; 42 | SpacesFollowedByAccount spaces_followed_by_account = 14; 43 | AccountFollowers account_followers = 15; 44 | AccountsFollowedByAccount accounts_followed_by_account = 16; 45 | GeneratedAccount generated_account = 17; 46 | ImportedAccount imported_account = 18; 47 | } 48 | } 49 | 50 | message Error { 51 | enum Kind { 52 | KIND_UNKNOWN = 0; 53 | KIND_NETWORK = 1; 54 | KIND_INVALID_PROTO = 2; 55 | KIND_INVALID_REQUEST = 3; 56 | KIND_NOT_FOUND = 4; 57 | KIND_SUBXT = 5; 58 | } 59 | Kind kind = 1; 60 | string msg = 2; 61 | } 62 | 63 | // REQUESTS 64 | 65 | message GetSpaceById { 66 | uint64 space_id = 1; 67 | } 68 | 69 | message GetSpaceByHandle { 70 | string handle = 1; 71 | } 72 | 73 | message GetPostById { 74 | uint64 post_id = 1; 75 | } 76 | 77 | message GetReactionById { 78 | uint64 reaction_id = 1; 79 | } 80 | 81 | message GetPostIdsBySpaceId { 82 | uint64 space_id = 1; 83 | } 84 | 85 | message GetReactionIdsByPostId { 86 | uint64 post_id = 1; 87 | } 88 | 89 | message GetReplyIdsByPostId { 90 | uint64 post_id = 1; 91 | } 92 | 93 | message GetSocialAccountByAccountId { 94 | string account_id = 1; 95 | } 96 | 97 | message GetNextSpaceId {} 98 | 99 | message GetNextPostId {} 100 | 101 | message GetSpaceIdsByOwner { 102 | string account_id = 1; 103 | } 104 | 105 | message GetSpaceFollowers { 106 | uint64 space_id = 1; 107 | } 108 | 109 | message GetSpacesFollowedByAccount { 110 | string account_id = 1; 111 | } 112 | 113 | message GetAccountFollowers { 114 | string account_id = 1; 115 | } 116 | 117 | message GetAccountsFollowedByAccount { 118 | string account_id = 1; 119 | } 120 | 121 | message GenerateAccount { 122 | string password = 1; 123 | } 124 | 125 | message ImportAccount { 126 | string password = 1; 127 | string suri = 2; 128 | } 129 | 130 | // DATA 131 | 132 | message WhoAndWhen { 133 | string account = 1; 134 | uint64 block_number = 2; 135 | uint64 time = 3; 136 | } 137 | 138 | message Content { 139 | oneof value { 140 | bytes raw = 1; 141 | string ipfs = 2; 142 | string hyper = 3; 143 | } 144 | } 145 | 146 | message PostExtension { 147 | oneof value { 148 | Comment comment = 1; 149 | SharedPost shared_post = 2; 150 | } 151 | } 152 | 153 | message Space { 154 | uint64 id = 1; 155 | WhoAndWhen created = 2; 156 | WhoAndWhen updated = 3; 157 | string owner = 4; 158 | uint64 parent_id = 5; 159 | string handle = 6; 160 | Content content = 7; 161 | bool hidden = 8; 162 | uint32 posts_count = 9; 163 | uint32 hidden_posts_count = 10; 164 | uint32 followers_count = 11; 165 | int32 score = 12; 166 | } 167 | 168 | message Post { 169 | uint64 id = 1; 170 | WhoAndWhen created = 2; 171 | WhoAndWhen updated = 3; 172 | string owner = 4; 173 | PostExtension extension_value = 5; 174 | uint64 space_id = 6; 175 | Content content = 7; 176 | bool hidden = 8; 177 | uint32 replies_count = 9; 178 | uint32 hidden_replies_count = 10; 179 | uint32 shares_count = 11; 180 | uint32 upvotes_count = 12; 181 | uint32 downvotes_count = 13; 182 | int32 score = 14; 183 | } 184 | 185 | message Comment { 186 | uint64 parent_id = 1; 187 | uint64 root_post_id = 2; 188 | } 189 | 190 | message SharedPost { 191 | uint64 root_post_id = 1; 192 | } 193 | 194 | message Reaction { 195 | enum ReactionKind { 196 | UNKNOWN = 0; 197 | UP_VOTE = 1; 198 | DOWN_VOTE = 2; 199 | } 200 | uint64 id = 1; 201 | WhoAndWhen created = 2; 202 | WhoAndWhen updated = 3; 203 | ReactionKind kind = 4; 204 | } 205 | 206 | message SocialAccount { 207 | uint32 followers_count = 1; 208 | uint32 following_accounts_count = 2; 209 | uint32 following_spaces_count = 3; 210 | uint32 reputation = 4; 211 | Profile profile = 5; 212 | } 213 | 214 | message Profile { 215 | WhoAndWhen created = 1; 216 | WhoAndWhen updated = 2; 217 | Content content = 3; 218 | } 219 | 220 | // RESPONSES 221 | 222 | 223 | message SpaceById { 224 | Space space = 1; 225 | } 226 | 227 | message SpaceByHandle { 228 | Space space = 1; 229 | } 230 | 231 | message PostById { 232 | Post post = 1; 233 | } 234 | 235 | message ReactionById { 236 | Reaction reaction = 1; 237 | } 238 | 239 | message PostIdsBySpaceId { 240 | repeated uint64 post_ids = 1 [packed = true]; 241 | } 242 | 243 | message ReactionIdsByPostId { 244 | repeated uint64 reaction_ids = 1 [packed = true]; 245 | } 246 | 247 | message ReplyIdsByPostId { 248 | repeated uint64 reply_ids = 1 [packed = true]; 249 | } 250 | 251 | message SocialAccountByAccountId { 252 | SocialAccount social_account = 1; 253 | } 254 | 255 | message NextSpaceId { 256 | uint64 id = 1; 257 | } 258 | 259 | message NextPostId { 260 | uint64 id = 1; 261 | } 262 | 263 | message SpaceIdsByOwner { 264 | repeated uint64 space_ids = 1 [packed = true]; 265 | } 266 | 267 | message SpaceFollowers { 268 | repeated string account_ids = 1; 269 | } 270 | 271 | message SpacesFollowedByAccount { 272 | repeated uint64 space_ids = 1 [packed = true]; 273 | } 274 | 275 | message AccountFollowers { 276 | repeated string account_ids = 1; 277 | } 278 | 279 | message AccountsFollowedByAccount { 280 | repeated string account_ids = 1; 281 | } 282 | 283 | message GeneratedAccount { 284 | string public_key = 1; 285 | string seed_phrase = 2; 286 | } 287 | 288 | message ImportedAccount { 289 | string public_key = 1; 290 | } 291 | -------------------------------------------------------------------------------- /native/ffi/src/dart_utils.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::NonNull; 2 | 3 | /// Owned version of Dart's [Uint8List] in Rust. 4 | /// 5 | /// **Note**: Automatically frees the underlying memory allocated from Dart. 6 | #[repr(C)] 7 | pub struct Uint8List { 8 | buf: NonNull, 9 | len: usize, 10 | } 11 | 12 | impl AsRef<[u8]> for Uint8List { 13 | fn as_ref(&self) -> &[u8] { 14 | self.as_slice() 15 | } 16 | } 17 | 18 | impl Uint8List { 19 | pub fn as_slice(&self) -> &[u8] { 20 | unsafe { core::slice::from_raw_parts(self.buf.as_ptr(), self.len) } 21 | } 22 | } 23 | 24 | impl Drop for Uint8List { 25 | fn drop(&mut self) { 26 | let owned = unsafe { 27 | Vec::from_raw_parts(self.buf.as_ptr(), self.len, self.len) 28 | }; 29 | drop(owned); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /native/ffi/src/handler.rs: -------------------------------------------------------------------------------- 1 | use prost::Message; 2 | use sdk::pallet::*; 3 | use sdk::runtime::SubsocialRuntime; 4 | use sdk::subxt::sp_core::Pair; 5 | use sdk::subxt::sp_runtime::AccountId32; 6 | use sdk::subxt::Client; 7 | 8 | use crate::pb::subsocial::request::Body as RequestBody; 9 | use crate::pb::subsocial::response::Body as ResponseBody; 10 | use crate::pb::subsocial::*; 11 | use crate::transformer::*; 12 | 13 | pub async fn handle( 14 | client: &Client, 15 | req: Request, 16 | ) -> Vec { 17 | let body = match req.body { 18 | Some(v) => v, 19 | None => { 20 | let mut bytes = Vec::new(); 21 | let kind = error::Kind::InvalidRequest.into(); 22 | Error { 23 | kind, 24 | msg: String::from("Empty body"), 25 | } 26 | .encode(&mut bytes) 27 | .expect("should never fails"); 28 | return bytes; 29 | } 30 | }; 31 | let result = match body { 32 | RequestBody::NextSpaceId(_) => next_space_id(client).await, 33 | RequestBody::NextPostId(_) => next_post_id(client).await, 34 | RequestBody::SpaceById(args) => { 35 | space_by_id(client, args.space_id).await 36 | } 37 | RequestBody::SpaceByHandle(args) => { 38 | space_by_handle(client, args.handle).await 39 | } 40 | RequestBody::SpaceIdsByOwner(args) => { 41 | space_ids_by_owner(client, args.account_id).await 42 | } 43 | RequestBody::PostIdsBySpaceId(args) => { 44 | posts_ids_by_space_id(client, args.space_id).await 45 | } 46 | RequestBody::PostById(args) => post_by_id(client, args.post_id).await, 47 | RequestBody::ReactionIdsByPostId(args) => { 48 | reactions_ids_by_post_id(client, args.post_id).await 49 | } 50 | RequestBody::ReactionById(args) => { 51 | reaction_by_id(client, args.reaction_id).await 52 | } 53 | RequestBody::ReplyIdsByPostId(args) => { 54 | reply_ids_by_post_id(client, args.post_id).await 55 | } 56 | RequestBody::SocialAccountByAccountId(args) => { 57 | social_account_by_account_id(client, args.account_id).await 58 | } 59 | RequestBody::SpaceFollowers(args) => { 60 | space_followers(client, args.space_id).await 61 | } 62 | RequestBody::SpacesFollowedByAccount(args) => { 63 | spaces_followed_by_account(client, args.account_id).await 64 | } 65 | RequestBody::AccountFollowers(args) => { 66 | account_followers(client, args.account_id).await 67 | } 68 | RequestBody::AccountsFollowedByAccount(args) => { 69 | accounts_followed_by_account(client, args.account_id).await 70 | } 71 | RequestBody::GenerateAccount(args) => generate_account(args), 72 | RequestBody::ImportAccount(args) => import_account(args), 73 | }; 74 | let response = match result { 75 | Ok(body) => Response { body: Some(body) }, 76 | Err(e) => Response { 77 | body: Some(ResponseBody::Error(e)), 78 | }, 79 | }; 80 | let mut buffer = Vec::new(); 81 | response.encode(&mut buffer).expect("should never fails"); 82 | buffer 83 | } 84 | 85 | async fn space_by_id( 86 | client: &Client, 87 | space_id: u64, 88 | ) -> Result { 89 | let store = spaces::SpaceByIdStore::new(space_id); 90 | let maybe_space = client.fetch(&store, None).await?; 91 | match maybe_space { 92 | Some(space) => { 93 | let body = ResponseBody::SpaceById(SpaceById { 94 | space: Some(space.into()), 95 | }); 96 | Ok(body) 97 | } 98 | None => Err(Error { 99 | kind: error::Kind::NotFound.into(), 100 | msg: String::from("Space Not Found"), 101 | }), 102 | } 103 | } 104 | 105 | async fn space_by_handle( 106 | client: &Client, 107 | handle: String, 108 | ) -> Result { 109 | let store = spaces::SpaceIdByHandleStore::new(handle); 110 | let maybe_space_id = client.fetch(&store, None).await?; 111 | match maybe_space_id { 112 | Some(space_id) => { 113 | let body = space_by_id(client, space_id).await?; 114 | let space = match body { 115 | ResponseBody::SpaceById(space_by_id) => space_by_id.space, 116 | _ => unreachable!(), 117 | }; 118 | let body = ResponseBody::SpaceByHandle(SpaceByHandle { space }); 119 | Ok(body) 120 | } 121 | None => Err(Error { 122 | kind: error::Kind::NotFound.into(), 123 | msg: String::from("Space Not Found"), 124 | }), 125 | } 126 | } 127 | 128 | async fn space_ids_by_owner( 129 | client: &Client, 130 | account_id: String, 131 | ) -> Result { 132 | let account_id = AccountId32::convert(account_id)?; 133 | let store = spaces::SpaceIdsByOwnerStore::new(account_id); 134 | let maybe_ids = client.fetch(&store, None).await?; 135 | match maybe_ids { 136 | Some(space_ids) => { 137 | let body = 138 | ResponseBody::SpaceIdsByOwner(SpaceIdsByOwner { space_ids }); 139 | Ok(body) 140 | } 141 | None => Err(Error { 142 | kind: error::Kind::NotFound.into(), 143 | msg: String::from("AccountId Not Found"), 144 | }), 145 | } 146 | } 147 | 148 | async fn posts_ids_by_space_id( 149 | client: &Client, 150 | space_id: u64, 151 | ) -> Result { 152 | let store = posts::PostIdsBySpaceIdStore::new(space_id); 153 | let maybe_ids = client.fetch(&store, None).await?; 154 | match maybe_ids { 155 | Some(ids) => { 156 | let body = ResponseBody::PostIdsBySpaceId(PostIdsBySpaceId { 157 | post_ids: ids, 158 | }); 159 | Ok(body) 160 | } 161 | None => Err(Error { 162 | kind: error::Kind::NotFound.into(), 163 | msg: String::from("Space Not Found"), 164 | }), 165 | } 166 | } 167 | 168 | async fn post_by_id( 169 | client: &Client, 170 | post_id: u64, 171 | ) -> Result { 172 | let store = posts::PostByIdStore::new(post_id); 173 | let maybe_post = client.fetch(&store, None).await?; 174 | match maybe_post { 175 | Some(post) => { 176 | let body = ResponseBody::PostById(PostById { 177 | post: Some(post.into()), 178 | }); 179 | Ok(body) 180 | } 181 | None => Err(Error { 182 | kind: error::Kind::NotFound.into(), 183 | msg: String::from("Post Not Found"), 184 | }), 185 | } 186 | } 187 | 188 | async fn reactions_ids_by_post_id( 189 | client: &Client, 190 | post_id: u64, 191 | ) -> Result { 192 | let store = reactions::ReactionIdsByPostIdStore::new(post_id); 193 | let maybe_ids = client.fetch(&store, None).await?; 194 | match maybe_ids { 195 | Some(ids) => { 196 | let body = ResponseBody::ReactionIdsByPostId(ReactionIdsByPostId { 197 | reaction_ids: ids, 198 | }); 199 | Ok(body) 200 | } 201 | None => Err(Error { 202 | kind: error::Kind::NotFound.into(), 203 | msg: String::from("Post Not Found"), 204 | }), 205 | } 206 | } 207 | 208 | async fn reaction_by_id( 209 | client: &Client, 210 | reaction_id: u64, 211 | ) -> Result { 212 | let store = reactions::ReactionByIdStore::new(reaction_id); 213 | let maybe_reaction = client.fetch(&store, None).await?; 214 | match maybe_reaction { 215 | Some(reaction) => { 216 | let body = ResponseBody::ReactionById(ReactionById { 217 | reaction: Some(reaction.into()), 218 | }); 219 | Ok(body) 220 | } 221 | None => Err(Error { 222 | kind: error::Kind::NotFound.into(), 223 | msg: String::from("Reaction Not Found"), 224 | }), 225 | } 226 | } 227 | 228 | async fn social_account_by_account_id( 229 | client: &Client, 230 | account_id: String, 231 | ) -> Result { 232 | let account_id = AccountId32::convert(account_id)?; 233 | let store = profiles::SocialAccountByIdStore::new(account_id); 234 | let maybe_account = client.fetch(&store, None).await?; 235 | match maybe_account { 236 | Some(account) => { 237 | let body = ResponseBody::SocialAccountByAccountId( 238 | SocialAccountByAccountId { 239 | social_account: Some(account.into()), 240 | }, 241 | ); 242 | Ok(body) 243 | } 244 | None => Err(Error { 245 | kind: error::Kind::NotFound.into(), 246 | msg: String::from("Social Account Not Found"), 247 | }), 248 | } 249 | } 250 | 251 | async fn reply_ids_by_post_id( 252 | client: &Client, 253 | post_id: u64, 254 | ) -> Result { 255 | let store = posts::ReplyIdsByPostIdStore::new(post_id); 256 | let maybe_ids = client.fetch(&store, None).await?; 257 | match maybe_ids { 258 | Some(ids) => { 259 | let body = ResponseBody::ReplyIdsByPostId(ReplyIdsByPostId { 260 | reply_ids: ids, 261 | }); 262 | Ok(body) 263 | } 264 | None => Err(Error { 265 | kind: error::Kind::NotFound.into(), 266 | msg: String::from("Post Not Found"), 267 | }), 268 | } 269 | } 270 | 271 | async fn next_space_id( 272 | client: &Client, 273 | ) -> Result { 274 | let store = spaces::NextSpaceIdStore::default(); 275 | let maybe_id = client.fetch(&store, None).await?; 276 | match maybe_id { 277 | Some(id) => { 278 | let body = ResponseBody::NextSpaceId(NextSpaceId { id }); 279 | Ok(body) 280 | } 281 | None => unreachable!(), 282 | } 283 | } 284 | 285 | async fn next_post_id( 286 | client: &Client, 287 | ) -> Result { 288 | let store = posts::NextPostIdStore::default(); 289 | let maybe_id = client.fetch(&store, None).await?; 290 | match maybe_id { 291 | Some(id) => { 292 | let body = ResponseBody::NextPostId(NextPostId { id }); 293 | Ok(body) 294 | } 295 | None => unreachable!(), 296 | } 297 | } 298 | 299 | async fn space_followers( 300 | client: &Client, 301 | space_id: u64, 302 | ) -> Result { 303 | let store = space_follows::SpaceFollowersStore::new(space_id); 304 | let maybe_account_ids = client.fetch(&store, None).await?; 305 | match maybe_account_ids { 306 | Some(account_ids) => { 307 | let body = ResponseBody::SpaceFollowers(SpaceFollowers { 308 | account_ids: account_ids 309 | .into_iter() 310 | .map(|v| v.to_string()) 311 | .collect(), 312 | }); 313 | Ok(body) 314 | } 315 | None => Err(Error { 316 | kind: error::Kind::NotFound.into(), 317 | msg: String::from("Space Not Found"), 318 | }), 319 | } 320 | } 321 | 322 | async fn spaces_followed_by_account( 323 | client: &Client, 324 | account_id: String, 325 | ) -> Result { 326 | let account_id = AccountId32::convert(account_id)?; 327 | let store = space_follows::SpacesFollowedByAccountStore::new(account_id); 328 | let maybe_space_ids = client.fetch(&store, None).await?; 329 | match maybe_space_ids { 330 | Some(space_ids) => { 331 | let body = ResponseBody::SpacesFollowedByAccount( 332 | SpacesFollowedByAccount { space_ids }, 333 | ); 334 | Ok(body) 335 | } 336 | None => Err(Error { 337 | kind: error::Kind::NotFound.into(), 338 | msg: String::from("AccountId Not Found"), 339 | }), 340 | } 341 | } 342 | 343 | async fn account_followers( 344 | client: &Client, 345 | account_id: String, 346 | ) -> Result { 347 | let account_id = AccountId32::convert(account_id)?; 348 | let store = profile_follows::AccountFollowersStore::new(account_id); 349 | let maybe_account_ids = client.fetch(&store, None).await?; 350 | match maybe_account_ids { 351 | Some(account_ids) => { 352 | let body = ResponseBody::AccountFollowers(AccountFollowers { 353 | account_ids: account_ids 354 | .into_iter() 355 | .map(|v| v.to_string()) 356 | .collect(), 357 | }); 358 | Ok(body) 359 | } 360 | None => Err(Error { 361 | kind: error::Kind::NotFound.into(), 362 | msg: String::from("AccountId Not Found"), 363 | }), 364 | } 365 | } 366 | 367 | async fn accounts_followed_by_account( 368 | client: &Client, 369 | account_id: String, 370 | ) -> Result { 371 | let account_id = AccountId32::convert(account_id)?; 372 | let store = 373 | profile_follows::AccountsFollowedByAccountStore::new(account_id); 374 | let maybe_account_ids = client.fetch(&store, None).await?; 375 | match maybe_account_ids { 376 | Some(account_ids) => { 377 | let body = ResponseBody::AccountsFollowedByAccount( 378 | AccountsFollowedByAccount { 379 | account_ids: account_ids 380 | .into_iter() 381 | .map(|v| v.to_string()) 382 | .collect(), 383 | }, 384 | ); 385 | Ok(body) 386 | } 387 | None => Err(Error { 388 | kind: error::Kind::NotFound.into(), 389 | msg: String::from("AccountId Not Found"), 390 | }), 391 | } 392 | } 393 | 394 | fn generate_account( 395 | GenerateAccount { password }: GenerateAccount, 396 | ) -> Result { 397 | let (pair, seed_phrase, _) = 398 | crate::Sr25519Pair::generate_with_phrase(if password.is_empty() { 399 | None 400 | } else { 401 | Some(password.as_str()) 402 | }); 403 | let pair = crate::subxt::PairSigner::new(pair); 404 | let public_key = pair.signer().public().to_string(); 405 | unsafe { 406 | let _old = crate::SIGNER.take(); 407 | crate::SIGNER 408 | .set(pair) 409 | .map_err(|_| ()) 410 | .expect("should never happen"); 411 | }; 412 | let body = ResponseBody::GeneratedAccount(GeneratedAccount { 413 | seed_phrase, 414 | public_key, 415 | }); 416 | Ok(body) 417 | } 418 | 419 | fn import_account( 420 | ImportAccount { password, suri }: ImportAccount, 421 | ) -> Result { 422 | let (pair, _) = crate::Sr25519Pair::from_string_with_seed( 423 | &suri, 424 | if password.is_empty() { 425 | None 426 | } else { 427 | Some(password.as_str()) 428 | }, 429 | ) 430 | .map_err(|_| { 431 | // TODO(shekohex): handle each case of this error with proper error message. 432 | crate::subxt::Error::Other(String::from("Invalid Phrase Format")) 433 | })?; 434 | let pair = crate::subxt::PairSigner::new(pair); 435 | let public_key = pair.signer().public().to_string(); 436 | unsafe { 437 | let _old = crate::SIGNER.take(); 438 | crate::SIGNER 439 | .set(pair) 440 | .map_err(|_| ()) 441 | .expect("should never happen"); 442 | }; 443 | let body = ResponseBody::ImportedAccount(ImportedAccount { public_key }); 444 | Ok(body) 445 | } 446 | -------------------------------------------------------------------------------- /native/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::os::raw::c_char; 3 | 4 | use allo_isolate::Isolate; 5 | use async_std::task; 6 | use once_cell::sync::OnceCell; 7 | use sdk::runtime::SubsocialRuntime; 8 | use sdk::subxt; 9 | 10 | mod dart_utils; 11 | mod handler; 12 | mod pb; 13 | mod transformer; 14 | 15 | use dart_utils::Uint8List; 16 | use pb::subsocial; 17 | use prost::Message; 18 | use sdk::subxt::sp_core::sr25519::Pair as Sr25519Pair; 19 | 20 | /// Global Shared [subxt::Client] between all tasks. 21 | static CLIENT: OnceCell> = OnceCell::new(); 22 | /// Global Shared [subxt::PairSigner] between all tasks. 23 | static mut SIGNER: OnceCell> = 24 | OnceCell::new(); 25 | 26 | #[derive(Debug, Clone)] 27 | #[repr(C)] 28 | pub struct SubscoialConfig { 29 | url: *const c_char, 30 | } 31 | 32 | #[no_mangle] 33 | pub extern "C" fn subsocial_init_client( 34 | port: i64, 35 | config: Box, 36 | ) -> i32 { 37 | let isolate = Isolate::new(port); 38 | // check if we already have a client 39 | if CLIENT.get().is_some() { 40 | isolate.post(()); 41 | return 1; // we are good! 42 | } 43 | let url = unsafe { 44 | CStr::from_ptr(config.url) 45 | .to_str() 46 | .unwrap_or("wss://rpc.subsocial.network") 47 | }; 48 | let task = isolate.catch_unwind(async move { 49 | let client = subxt::ClientBuilder::new().set_url(url).build().await?; 50 | CLIENT.set(client).map_err(|_| { 51 | subxt::Error::Other(String::from("Client already initialized")) 52 | })?; 53 | Result::<_, subxt::Error>::Ok(()) 54 | }); 55 | task::spawn(task); 56 | 1 57 | } 58 | 59 | #[no_mangle] 60 | pub extern "C" fn subsocial_dispatch(port: i64, buffer: Box) -> i32 { 61 | let isolate = Isolate::new(port); 62 | let req = match prost::Message::decode(buffer.as_slice()) { 63 | Ok(v) => v, 64 | Err(e) => { 65 | let mut bytes = Vec::new(); 66 | let kind = subsocial::error::Kind::InvalidProto.into(); 67 | subsocial::Error { 68 | kind, 69 | msg: e.to_string(), 70 | } 71 | .encode(&mut bytes) 72 | .expect("should never fails"); 73 | isolate.post(bytes); 74 | return 0xbadc0de; 75 | } 76 | }; 77 | let client = match CLIENT.get() { 78 | Some(v) => v, 79 | None => return 0xdead, 80 | }; 81 | let task = isolate.catch_unwind(handler::handle(client, req)); 82 | task::spawn(task); 83 | 1 84 | } 85 | 86 | /// a no-op function that forces xcode to link to our lib. 87 | /// ## Safety 88 | /// lol 89 | #[inline(never)] 90 | #[no_mangle] 91 | pub unsafe extern "C" fn subsocial_link_me_plz() {} 92 | -------------------------------------------------------------------------------- /native/ffi/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, clippy::large_enum_variant)] 2 | 3 | pub mod subsocial; 4 | -------------------------------------------------------------------------------- /native/ffi/src/pb/subsoical.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, ::prost::Message)] 2 | pub struct Request { 3 | #[prost(oneof = "request::Body", tags = "1, 2, 3, 4, 5, 6")] 4 | pub body: ::core::option::Option, 5 | } 6 | /// Nested message and enum types in `Request`. 7 | pub mod request { 8 | #[derive(Clone, PartialEq, ::prost::Oneof)] 9 | pub enum Body { 10 | #[prost(message, tag = "1")] 11 | SpaceById(super::GetSpaceById), 12 | #[prost(message, tag = "2")] 13 | SpaceByHandle(super::GetSpaceByHandle), 14 | #[prost(message, tag = "3")] 15 | PostById(super::GetPostById), 16 | #[prost(message, tag = "4")] 17 | PostIdsBySpaceId(super::GetPostIdsBySpaceId), 18 | #[prost(message, tag = "5")] 19 | ReactionById(super::GetReactionById), 20 | #[prost(message, tag = "6")] 21 | ReactionIdsByPostId(super::GetReactionIdsByPostId), 22 | } 23 | } 24 | #[derive(Clone, PartialEq, ::prost::Message)] 25 | pub struct Response { 26 | #[prost(oneof = "response::Body", tags = "1, 2, 3, 4, 5, 6, 7")] 27 | pub body: ::core::option::Option, 28 | } 29 | /// Nested message and enum types in `Response`. 30 | pub mod response { 31 | #[derive(Clone, PartialEq, ::prost::Oneof)] 32 | pub enum Body { 33 | #[prost(message, tag = "1")] 34 | Error(super::Error), 35 | #[prost(message, tag = "2")] 36 | SpaceById(super::SpaceById), 37 | #[prost(message, tag = "3")] 38 | SpaceByHandle(super::SpaceByByHandle), 39 | #[prost(message, tag = "4")] 40 | PostById(super::PostById), 41 | #[prost(message, tag = "5")] 42 | PostIdsBySpaceId(super::PostIdsBySpaceId), 43 | #[prost(message, tag = "6")] 44 | ReactionById(super::ReactionById), 45 | #[prost(message, tag = "7")] 46 | ReactionIdsByPostId(super::ReactionIdsByPostId), 47 | } 48 | } 49 | #[derive(Clone, PartialEq, ::prost::Message)] 50 | pub struct Error { 51 | #[prost(enumeration = "error::Kind", tag = "1")] 52 | pub kind: i32, 53 | #[prost(string, tag = "2")] 54 | pub msg: ::prost::alloc::string::String, 55 | } 56 | /// Nested message and enum types in `Error`. 57 | pub mod error { 58 | #[derive( 59 | Clone, 60 | Copy, 61 | Debug, 62 | PartialEq, 63 | Eq, 64 | Hash, 65 | PartialOrd, 66 | Ord, 67 | ::prost::Enumeration, 68 | )] 69 | #[repr(i32)] 70 | pub enum Kind { 71 | Unknown = 0, 72 | Network = 1, 73 | InvalidProto = 2, 74 | InvalidRequest = 3, 75 | } 76 | } 77 | #[derive(Clone, PartialEq, ::prost::Message)] 78 | pub struct GetSpaceById { 79 | #[prost(uint64, tag = "1")] 80 | pub space_id: u64, 81 | } 82 | #[derive(Clone, PartialEq, ::prost::Message)] 83 | pub struct GetSpaceByHandle { 84 | #[prost(string, tag = "1")] 85 | pub handle: ::prost::alloc::string::String, 86 | } 87 | #[derive(Clone, PartialEq, ::prost::Message)] 88 | pub struct GetPostById { 89 | #[prost(uint64, tag = "1")] 90 | pub post_id: u64, 91 | } 92 | #[derive(Clone, PartialEq, ::prost::Message)] 93 | pub struct GetReactionById { 94 | #[prost(uint64, tag = "1")] 95 | pub reaction_id: u64, 96 | } 97 | #[derive(Clone, PartialEq, ::prost::Message)] 98 | pub struct GetPostIdsBySpaceId { 99 | #[prost(uint64, tag = "1")] 100 | pub space_id: u64, 101 | } 102 | #[derive(Clone, PartialEq, ::prost::Message)] 103 | pub struct GetReactionIdsByPostId { 104 | #[prost(uint64, tag = "1")] 105 | pub post_id: u64, 106 | } 107 | #[derive(Clone, PartialEq, ::prost::Message)] 108 | pub struct Space { 109 | #[prost(uint64, tag = "1")] 110 | pub id: u64, 111 | } 112 | #[derive(Clone, PartialEq, ::prost::Message)] 113 | pub struct Post { 114 | #[prost(uint64, tag = "1")] 115 | pub id: u64, 116 | } 117 | #[derive(Clone, PartialEq, ::prost::Message)] 118 | pub struct Reaction { 119 | #[prost(uint64, tag = "1")] 120 | pub id: u64, 121 | } 122 | #[derive(Clone, PartialEq, ::prost::Message)] 123 | pub struct SpaceById { 124 | #[prost(message, optional, tag = "1")] 125 | pub space: ::core::option::Option, 126 | } 127 | #[derive(Clone, PartialEq, ::prost::Message)] 128 | pub struct SpaceByByHandle { 129 | #[prost(message, optional, tag = "1")] 130 | pub space: ::core::option::Option, 131 | } 132 | #[derive(Clone, PartialEq, ::prost::Message)] 133 | pub struct PostById { 134 | #[prost(message, optional, tag = "1")] 135 | pub post: ::core::option::Option, 136 | } 137 | #[derive(Clone, PartialEq, ::prost::Message)] 138 | pub struct ReactionById { 139 | #[prost(message, optional, tag = "1")] 140 | pub reaction: ::core::option::Option, 141 | } 142 | #[derive(Clone, PartialEq, ::prost::Message)] 143 | pub struct PostIdsBySpaceId { 144 | #[prost(uint64, repeated, tag = "1")] 145 | pub post_id: ::prost::alloc::vec::Vec, 146 | } 147 | #[derive(Clone, PartialEq, ::prost::Message)] 148 | pub struct ReactionIdsByPostId { 149 | #[prost(uint64, repeated, tag = "1")] 150 | pub reaction_id: ::prost::alloc::vec::Vec, 151 | } 152 | -------------------------------------------------------------------------------- /native/ffi/src/transformer.rs: -------------------------------------------------------------------------------- 1 | use sdk::pallet::{posts, profiles, reactions, spaces, utils}; 2 | use sdk::runtime::SubsocialRuntime; 3 | use sdk::subxt::sp_core::crypto::Ss58Codec; 4 | use sdk::subxt::sp_runtime::AccountId32; 5 | 6 | use crate::pb::subsocial::*; 7 | 8 | pub trait AccountIdFromString: Ss58Codec { 9 | fn convert(val: String) -> Result { 10 | match Self::from_string(&val) { 11 | Ok(val) => Ok(val), 12 | Err(_) => Err(Error { 13 | kind: error::Kind::InvalidRequest.into(), 14 | msg: String::from("Invalid AccountId"), 15 | }), 16 | } 17 | } 18 | } 19 | 20 | impl AccountIdFromString for AccountId32 {} 21 | 22 | impl From for Error { 23 | fn from(e: sdk::subxt::Error) -> Self { 24 | Self { 25 | kind: error::Kind::Subxt.into(), 26 | msg: e.to_string(), 27 | } 28 | } 29 | } 30 | 31 | impl From> for WhoAndWhen { 32 | fn from(v: utils::WhoAndWhen) -> Self { 33 | Self { 34 | account: v.account.to_string(), 35 | block_number: v.block.into(), 36 | time: v.time, 37 | } 38 | } 39 | } 40 | 41 | impl From for Content { 42 | fn from(content: utils::Content) -> Self { 43 | use content::Value; 44 | match content { 45 | utils::Content::None => unimplemented!("Should not be called"), 46 | utils::Content::Raw(value) => Content { 47 | value: Some(Value::Raw(value)), 48 | }, 49 | utils::Content::IPFS(cid) => Content { 50 | value: Some(Value::Ipfs(String::from_utf8(cid).unwrap())), 51 | }, 52 | utils::Content::Hyper(link) => Content { 53 | value: Some(Value::Hyper(String::from_utf8(link).unwrap())), 54 | }, 55 | } 56 | } 57 | } 58 | 59 | impl From for PostExtension { 60 | fn from(extension: posts::PostExtension) -> Self { 61 | use post_extension::Value; 62 | match extension { 63 | posts::PostExtension::Comment(comment) => PostExtension { 64 | value: Some(Value::Comment(Comment { 65 | parent_id: comment.parent_id.unwrap_or_default(), 66 | root_post_id: comment.root_post_id, 67 | })), 68 | }, 69 | posts::PostExtension::SharedPost(id) => PostExtension { 70 | value: Some(Value::SharedPost(SharedPost { root_post_id: id })), 71 | }, 72 | posts::PostExtension::RegularPost => { 73 | unimplemented!("Should be None!") 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl From> for Space { 80 | fn from(space: spaces::Space) -> Self { 81 | use utils::Content; 82 | Self { 83 | id: space.id, 84 | created: Some(space.created.into()), 85 | updated: space.updated.map(Into::into), 86 | owner: space.owner.to_string(), 87 | parent_id: space.parent_id.unwrap_or_default(), 88 | handle: space 89 | .handle 90 | .map(String::from_utf8) 91 | .transpose() 92 | .unwrap_or_default() 93 | .unwrap_or_default(), 94 | content: if space.content == Content::None { 95 | None 96 | } else { 97 | Some(space.content.into()) 98 | }, 99 | hidden: space.hidden, 100 | posts_count: space.posts_count, 101 | hidden_posts_count: space.hidden_posts_count, 102 | followers_count: space.followers_count, 103 | score: space.score, 104 | } 105 | } 106 | } 107 | 108 | impl From> for Post { 109 | fn from(post: posts::Post) -> Self { 110 | use posts::PostExtension; 111 | use utils::Content; 112 | Self { 113 | id: post.id, 114 | created: Some(post.created.into()), 115 | updated: post.updated.map(Into::into), 116 | owner: post.owner.to_string(), 117 | space_id: post.space_id.unwrap_or_default(), 118 | content: if post.content == Content::None { 119 | None 120 | } else { 121 | Some(post.content.into()) 122 | }, 123 | extension_value: if post.extension == PostExtension::RegularPost { 124 | None 125 | } else { 126 | Some(post.extension.into()) 127 | }, 128 | hidden: post.hidden, 129 | replies_count: post.replies_count.into(), 130 | shares_count: post.shares_count.into(), 131 | upvotes_count: post.upvotes_count.into(), 132 | downvotes_count: post.downvotes_count.into(), 133 | hidden_replies_count: post.hidden_replies_count.into(), 134 | score: post.score, 135 | } 136 | } 137 | } 138 | 139 | impl From for reaction::ReactionKind { 140 | fn from(k: reactions::ReactionKind) -> Self { 141 | match k { 142 | reactions::ReactionKind::Upvote => reaction::ReactionKind::UpVote, 143 | reactions::ReactionKind::Downvote => { 144 | reaction::ReactionKind::DownVote 145 | } 146 | } 147 | } 148 | } 149 | 150 | impl From> for Reaction { 151 | fn from(reaction: reactions::Reaction) -> Self { 152 | use reaction::ReactionKind; 153 | Self { 154 | id: reaction.id, 155 | created: Some(reaction.created.into()), 156 | updated: reaction.updated.map(Into::into), 157 | kind: ReactionKind::from(reaction.kind).into(), 158 | } 159 | } 160 | } 161 | 162 | impl From> for Profile { 163 | fn from(profile: profiles::Profile) -> Self { 164 | Self { 165 | created: Some(profile.created.into()), 166 | updated: profile.updated.map(Into::into), 167 | content: Some(profile.content.into()), 168 | } 169 | } 170 | } 171 | 172 | impl From> for SocialAccount { 173 | fn from(account: profiles::SocialAccount) -> Self { 174 | Self { 175 | profile: account.profile.map(Into::into), 176 | followers_count: account.followers_count, 177 | reputation: account.reputation, 178 | following_spaces_count: account.following_spaces_count.into(), 179 | following_accounts_count: account.following_accounts_count.into(), 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /native/sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subsocial-sdk" 3 | version = "0.1.0" 4 | authors = ["Shady Khalifa "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | subxt = { version = "0.14", package = "substrate-subxt" } 9 | codec = { package = "parity-scale-codec", version = "1.3.4", features = ["derive", "full"] } 10 | 11 | # Pallets 12 | # Substrate dependencies 13 | frame-support = { default-features = false, version = '2.0.1' } 14 | frame-system = { default-features = false, version = '2.0.1' } 15 | pallet-timestamp = { default-features = false, version = '2.0.1' } 16 | sp-std = { default-features = false, version = '2.0.1' } 17 | 18 | [dev-dependencies] 19 | async-std = { version = "1.8", features = ["attributes"] } 20 | -------------------------------------------------------------------------------- /native/sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod pallet; 2 | pub mod runtime; 3 | 4 | pub use subxt; 5 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod permissions; 2 | pub mod posts; 3 | pub mod profile_follows; 4 | pub mod profiles; 5 | pub mod reactions; 6 | pub mod space_follows; 7 | pub mod spaces; 8 | pub mod traits; 9 | pub mod utils; 10 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/permissions.rs: -------------------------------------------------------------------------------- 1 | use codec::{Decode, Encode}; 2 | use frame_support::traits::Get; 3 | use frame_system as system; 4 | use sp_std::collections::btree_set::BTreeSet; 5 | use sp_std::prelude::*; 6 | use subxt::sp_runtime::RuntimeDebug; 7 | 8 | use super::utils::SpaceId; 9 | 10 | #[derive( 11 | Encode, Decode, Ord, PartialOrd, Clone, Eq, PartialEq, RuntimeDebug, 12 | )] 13 | pub enum SpacePermission { 14 | /// Create, update, delete, grant and revoke roles in this space. 15 | ManageRoles, 16 | 17 | /// Act on behalf of this space within this space. 18 | RepresentSpaceInternally, 19 | /// Act on behalf of this space outside of this space. 20 | RepresentSpaceExternally, 21 | 22 | /// Update this space. 23 | UpdateSpace, 24 | 25 | // Related to subspaces in this space: 26 | CreateSubspaces, 27 | UpdateOwnSubspaces, 28 | DeleteOwnSubspaces, 29 | HideOwnSubspaces, 30 | 31 | UpdateAnySubspace, 32 | DeleteAnySubspace, 33 | HideAnySubspace, 34 | 35 | // Related to posts in this space: 36 | CreatePosts, 37 | UpdateOwnPosts, 38 | DeleteOwnPosts, 39 | HideOwnPosts, 40 | 41 | UpdateAnyPost, 42 | DeleteAnyPost, 43 | HideAnyPost, 44 | 45 | // Related to comments in this space: 46 | CreateComments, 47 | UpdateOwnComments, 48 | DeleteOwnComments, 49 | HideOwnComments, 50 | 51 | // NOTE: It was made on purpose that it's not possible to update or delete 52 | // not own comments. Instead it's possible to allow to hide and block 53 | // comments. 54 | HideAnyComment, 55 | 56 | /// Upvote any post or comment in this space. 57 | Upvote, 58 | /// Downvote any post or comment in this space. 59 | Downvote, 60 | /// Share any post or comment from this space to another outer space. 61 | Share, 62 | 63 | /// Override permissions per subspace in this space. 64 | OverrideSubspacePermissions, 65 | /// Override permissions per post in this space. 66 | OverridePostPermissions, 67 | 68 | // Related to the moderation pallet: 69 | /// Suggest new entity status in space (whether it's blocked or allowed) 70 | SuggestEntityStatus, 71 | /// Update entity status in space 72 | UpdateEntityStatus, 73 | 74 | // Related to space settings: 75 | /// Allows to update space settings across different pallets. 76 | UpdateSpaceSettings, 77 | } 78 | 79 | pub type SpacePermissionSet = BTreeSet; 80 | 81 | #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)] 82 | pub struct SpacePermissions { 83 | pub none: Option, 84 | pub everyone: Option, 85 | pub follower: Option, 86 | pub space_owner: Option, 87 | } 88 | 89 | impl Default for SpacePermissions { 90 | fn default() -> SpacePermissions { 91 | SpacePermissions { 92 | none: None, 93 | everyone: None, 94 | follower: None, 95 | space_owner: None, 96 | } 97 | } 98 | } 99 | 100 | #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)] 101 | pub struct SpacePermissionsContext { 102 | pub space_id: SpaceId, 103 | pub is_space_owner: bool, 104 | pub is_space_follower: bool, 105 | pub space_perms: Option, 106 | } 107 | 108 | /// The pallet's configuration trait. 109 | pub trait Trait: system::Trait { 110 | type DefaultSpacePermissions: Get; 111 | } 112 | 113 | impl SpacePermission { 114 | pub fn is_present_in_role( 115 | &self, 116 | perms_opt: Option, 117 | ) -> bool { 118 | if let Some(perms) = perms_opt { 119 | if perms.contains(self) { 120 | return true; 121 | } 122 | } 123 | false 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/posts.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::marker::PhantomData; 3 | 4 | use codec::{Decode, Encode}; 5 | use subxt::system::*; 6 | 7 | use super::utils::{Content, SpaceId, WhoAndWhen}; 8 | 9 | #[subxt::module] 10 | pub trait Posts: System {} 11 | 12 | pub type PostId = u64; 13 | 14 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 15 | pub struct Post { 16 | pub id: PostId, 17 | pub created: WhoAndWhen, 18 | pub updated: Option>, 19 | pub owner: T::AccountId, 20 | pub extension: PostExtension, 21 | pub space_id: Option, 22 | pub content: Content, 23 | pub hidden: bool, 24 | pub replies_count: u16, 25 | pub hidden_replies_count: u16, 26 | pub shares_count: u16, 27 | pub upvotes_count: u16, 28 | pub downvotes_count: u16, 29 | pub score: i32, 30 | } 31 | 32 | impl fmt::Display for Post { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "Post #{} on IPFS: {}", self.id, self.content)?; 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)] 40 | pub enum PostExtension { 41 | RegularPost, 42 | Comment(Comment), 43 | SharedPost(PostId), 44 | } 45 | 46 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)] 47 | pub struct Comment { 48 | pub parent_id: Option, 49 | pub root_post_id: PostId, 50 | } 51 | 52 | // Storage .. 53 | 54 | #[derive(Clone, Debug, Eq, Default, Encode, PartialEq, subxt::Store)] 55 | pub struct NextPostIdStore { 56 | #[store(returns = PostId)] 57 | __marker: PhantomData, 58 | } 59 | 60 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 61 | pub struct PostByIdStore { 62 | #[store(returns = Post)] 63 | post_id: PostId, 64 | __marker: PhantomData, 65 | } 66 | 67 | impl PostByIdStore { 68 | pub fn new(post_id: PostId) -> Self { 69 | Self { 70 | post_id, 71 | __marker: Default::default(), 72 | } 73 | } 74 | } 75 | 76 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 77 | pub struct PostIdsBySpaceIdStore { 78 | #[store(returns = Vec)] 79 | space_id: SpaceId, 80 | __marker: PhantomData, 81 | } 82 | 83 | impl PostIdsBySpaceIdStore { 84 | pub fn new(space_id: SpaceId) -> Self { 85 | Self { 86 | space_id, 87 | __marker: Default::default(), 88 | } 89 | } 90 | } 91 | 92 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 93 | pub struct ReplyIdsByPostIdStore { 94 | #[store(returns = Vec)] 95 | post_id: PostId, 96 | __marker: PhantomData, 97 | } 98 | 99 | impl ReplyIdsByPostIdStore { 100 | pub fn new(post_id: PostId) -> Self { 101 | Self { 102 | post_id, 103 | __marker: Default::default(), 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/profile_follows.rs: -------------------------------------------------------------------------------- 1 | use codec::Encode; 2 | use subxt::system::*; 3 | 4 | #[subxt::module] 5 | pub trait ProfileFollows: System {} 6 | 7 | // Storage .. 8 | 9 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 10 | pub struct AccountFollowersStore { 11 | #[store(returns = Vec)] 12 | account_id: T::AccountId, 13 | } 14 | 15 | impl AccountFollowersStore { 16 | pub fn new(account_id: T::AccountId) -> Self { 17 | Self { account_id } 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 22 | pub struct AccountsFollowedByAccountStore { 23 | #[store(returns = Vec)] 24 | account_id: T::AccountId, 25 | } 26 | 27 | impl AccountsFollowedByAccountStore { 28 | pub fn new(account_id: T::AccountId) -> Self { 29 | Self { account_id } 30 | } 31 | } 32 | 33 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 34 | pub struct AccountFollowedByAccountStore { 35 | #[store(returns = bool)] 36 | account_one_id: T::AccountId, 37 | account_two_id: T::AccountId, 38 | } 39 | 40 | impl AccountFollowedByAccountStore { 41 | pub fn new( 42 | account_one_id: T::AccountId, 43 | account_two_id: T::AccountId, 44 | ) -> Self { 45 | Self { 46 | account_one_id, 47 | account_two_id, 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/profiles.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use codec::{Decode, Encode}; 4 | use subxt::system::*; 5 | 6 | use super::utils::{Content, WhoAndWhen}; 7 | 8 | #[subxt::module] 9 | pub trait Profiles: System {} 10 | 11 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 12 | pub struct SocialAccount { 13 | pub followers_count: u32, 14 | pub following_accounts_count: u16, 15 | pub following_spaces_count: u16, 16 | pub reputation: u32, 17 | pub profile: Option>, 18 | } 19 | 20 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 21 | pub struct Profile { 22 | pub created: WhoAndWhen, 23 | pub updated: Option>, 24 | pub content: Content, 25 | } 26 | 27 | // Storage .. 28 | 29 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 30 | pub struct SocialAccountByIdStore { 31 | #[store(returns = SocialAccount)] 32 | account_id: T::AccountId, 33 | __marker: PhantomData, 34 | } 35 | 36 | impl SocialAccountByIdStore { 37 | pub fn new(account_id: T::AccountId) -> Self { 38 | Self { 39 | account_id, 40 | __marker: Default::default(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/reactions.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::marker::PhantomData; 3 | 4 | use codec::{Decode, Encode}; 5 | use subxt::system::*; 6 | 7 | use super::posts::PostId; 8 | use super::utils::WhoAndWhen; 9 | 10 | #[subxt::module] 11 | pub trait Reactions: System {} 12 | 13 | pub type ReactionId = u64; 14 | 15 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)] 16 | pub enum ReactionKind { 17 | Upvote, 18 | Downvote, 19 | } 20 | 21 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 22 | pub struct Reaction { 23 | pub id: ReactionId, 24 | pub created: WhoAndWhen, 25 | pub updated: Option>, 26 | pub kind: ReactionKind, 27 | } 28 | 29 | impl fmt::Display for Reaction { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | let emo = match self.kind { 32 | ReactionKind::Upvote => "👍", 33 | ReactionKind::Downvote => "👎", 34 | }; 35 | write!(f, "Reaction #{} {}", self.id, emo)?; 36 | Ok(()) 37 | } 38 | } 39 | 40 | // Storage .. 41 | 42 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 43 | pub struct ReactionByIdStore { 44 | #[store(returns = Reaction)] 45 | reaction_id: ReactionId, 46 | __marker: PhantomData, 47 | } 48 | 49 | impl ReactionByIdStore { 50 | pub fn new(reaction_id: ReactionId) -> Self { 51 | Self { 52 | reaction_id, 53 | __marker: Default::default(), 54 | } 55 | } 56 | } 57 | 58 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 59 | pub struct ReactionIdsByPostIdStore { 60 | #[store(returns = Vec)] 61 | post_id: PostId, 62 | __marker: PhantomData, 63 | } 64 | 65 | impl ReactionIdsByPostIdStore { 66 | pub fn new(post_id: PostId) -> Self { 67 | Self { 68 | post_id, 69 | __marker: Default::default(), 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/space_follows.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use codec::Encode; 4 | use subxt::system::*; 5 | 6 | use super::utils::SpaceId; 7 | 8 | #[subxt::module] 9 | pub trait SpaceFollows: System {} 10 | 11 | // Storage .. 12 | 13 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 14 | pub struct SpaceFollowersStore { 15 | #[store(returns = Vec)] 16 | space_id: SpaceId, 17 | __marker: PhantomData, 18 | } 19 | 20 | impl SpaceFollowersStore { 21 | pub fn new(space_id: SpaceId) -> Self { 22 | Self { 23 | space_id, 24 | __marker: Default::default(), 25 | } 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 30 | pub struct SpacesFollowedByAccountStore { 31 | #[store(returns = Vec)] 32 | account_id: T::AccountId, 33 | __marker: PhantomData, 34 | } 35 | 36 | impl SpacesFollowedByAccountStore { 37 | pub fn new(account_id: T::AccountId) -> Self { 38 | Self { 39 | account_id, 40 | __marker: Default::default(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/spaces.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::marker::PhantomData; 3 | 4 | use codec::{Decode, Encode}; 5 | use subxt::system::*; 6 | 7 | use super::permissions::SpacePermissions; 8 | use super::utils::{Content, SpaceId, WhoAndWhen}; 9 | 10 | #[subxt::module] 11 | pub trait Spaces: System {} 12 | 13 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 14 | pub struct Space { 15 | pub id: SpaceId, 16 | pub created: WhoAndWhen, 17 | pub updated: Option>, 18 | pub owner: T::AccountId, 19 | pub parent_id: Option, 20 | pub handle: Option>, 21 | pub content: Content, 22 | pub hidden: bool, 23 | pub posts_count: u32, 24 | pub hidden_posts_count: u32, 25 | pub followers_count: u32, 26 | pub score: i32, 27 | pub permissions: Option, 28 | } 29 | 30 | impl fmt::Display for Space { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | write!( 33 | f, 34 | "Space #{} with #{} Posts on IPFS: {}", 35 | self.id, self.posts_count, self.content 36 | )?; 37 | Ok(()) 38 | } 39 | } 40 | 41 | // Storage .. 42 | 43 | #[derive(Clone, Debug, Eq, Default, Encode, PartialEq, subxt::Store)] 44 | pub struct NextSpaceIdStore { 45 | #[store(returns = SpaceId)] 46 | __marker: PhantomData, 47 | } 48 | 49 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 50 | pub struct SpaceByIdStore { 51 | #[store(returns = Space)] 52 | space_id: SpaceId, 53 | __marker: PhantomData, 54 | } 55 | 56 | impl SpaceByIdStore { 57 | pub fn new(space_id: SpaceId) -> Self { 58 | Self { 59 | space_id, 60 | __marker: Default::default(), 61 | } 62 | } 63 | } 64 | 65 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 66 | pub struct SpaceIdByHandleStore { 67 | #[store(returns = SpaceId)] 68 | handle: Vec, 69 | __marker: PhantomData, 70 | } 71 | 72 | impl SpaceIdByHandleStore { 73 | pub fn new(handle: impl Into>) -> Self { 74 | Self { 75 | handle: handle.into(), 76 | __marker: Default::default(), 77 | } 78 | } 79 | } 80 | 81 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)] 82 | pub struct SpaceIdsByOwnerStore { 83 | #[store(returns = Vec)] 84 | account_id: T::AccountId, 85 | __marker: PhantomData, 86 | } 87 | 88 | impl SpaceIdsByOwnerStore { 89 | pub fn new(account_id: T::AccountId) -> Self { 90 | Self { 91 | account_id, 92 | __marker: Default::default(), 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/traits.rs: -------------------------------------------------------------------------------- 1 | use frame_support::dispatch::{DispatchError, DispatchResult}; 2 | 3 | use super::permissions::{ 4 | SpacePermission, SpacePermissions, SpacePermissionsContext, 5 | }; 6 | use super::utils::{Content, SpaceId, User}; 7 | 8 | /// Minimal set of fields from Space struct that are required by roles pallet. 9 | pub struct SpaceForRoles { 10 | pub owner: AccountId, 11 | pub permissions: Option, 12 | } 13 | 14 | pub trait SpaceForRolesProvider { 15 | type AccountId; 16 | 17 | fn get_space( 18 | id: SpaceId, 19 | ) -> Result, DispatchError>; 20 | } 21 | 22 | pub trait SpaceFollowsProvider { 23 | type AccountId; 24 | 25 | fn is_space_follower(account: Self::AccountId, space_id: SpaceId) -> bool; 26 | } 27 | 28 | pub trait PermissionChecker { 29 | type AccountId; 30 | 31 | fn ensure_user_has_space_permission( 32 | user: User, 33 | ctx: SpacePermissionsContext, 34 | permission: SpacePermission, 35 | error: DispatchError, 36 | ) -> DispatchResult; 37 | 38 | fn ensure_account_has_space_permission( 39 | account: Self::AccountId, 40 | ctx: SpacePermissionsContext, 41 | permission: SpacePermission, 42 | error: DispatchError, 43 | ) -> DispatchResult { 44 | Self::ensure_user_has_space_permission( 45 | User::Account(account), 46 | ctx, 47 | permission, 48 | error, 49 | ) 50 | } 51 | } 52 | 53 | pub trait IsAccountBlocked { 54 | fn is_blocked_account(account: AccountId, scope: SpaceId) -> bool; 55 | fn is_allowed_account(account: AccountId, scope: SpaceId) -> bool; 56 | } 57 | 58 | impl IsAccountBlocked for () { 59 | fn is_blocked_account(_account: AccountId, _scope: u64) -> bool { 60 | false 61 | } 62 | 63 | fn is_allowed_account(_account: AccountId, _scope: u64) -> bool { 64 | true 65 | } 66 | } 67 | 68 | pub trait IsSpaceBlocked { 69 | fn is_blocked_space(space_id: SpaceId, scope: SpaceId) -> bool; 70 | fn is_allowed_space(space_id: SpaceId, scope: SpaceId) -> bool; 71 | } 72 | 73 | // TODO: reuse `type PostId` from pallet_utils in future updates 74 | pub trait IsPostBlocked { 75 | fn is_blocked_post(post_id: PostId, scope: SpaceId) -> bool; 76 | fn is_allowed_post(post_id: PostId, scope: SpaceId) -> bool; 77 | } 78 | 79 | impl IsPostBlocked for () { 80 | fn is_blocked_post(_post_id: PostId, _scope: SpaceId) -> bool { 81 | false 82 | } 83 | 84 | fn is_allowed_post(_post_id: PostId, _scope: u64) -> bool { 85 | true 86 | } 87 | } 88 | 89 | pub trait IsContentBlocked { 90 | fn is_blocked_content(content: Content, scope: SpaceId) -> bool; 91 | fn is_allowed_content(content: Content, scope: SpaceId) -> bool; 92 | } 93 | 94 | impl IsContentBlocked for () { 95 | fn is_blocked_content(_content: Content, _scope: u64) -> bool { 96 | false 97 | } 98 | 99 | fn is_allowed_content(_content: Content, _scope: SpaceId) -> bool { 100 | true 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /native/sdk/src/pallet/utils.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use codec::{Decode, Encode}; 4 | 5 | use sp_std::prelude::*; 6 | use subxt::system::System; 7 | 8 | pub type SpaceId = u64; 9 | 10 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 11 | pub struct WhoAndWhen { 12 | pub account: T::AccountId, 13 | pub block: T::BlockNumber, 14 | pub time: u64, 15 | } 16 | 17 | #[derive(Encode, Decode, Ord, PartialOrd, Clone, Eq, PartialEq, Debug)] 18 | pub enum User { 19 | Account(AccountId), 20 | Space(SpaceId), 21 | } 22 | 23 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] 24 | pub enum Content { 25 | /// No content. 26 | None, 27 | /// A raw vector of bytes. 28 | Raw(Vec), 29 | /// IPFS CID as a String. 30 | IPFS(Vec), 31 | /// Hypercore protocol (former DAT) id of content. 32 | Hyper(Vec), 33 | } 34 | 35 | impl fmt::Display for Content { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | use Content::*; 38 | match self { 39 | None => write!(f, "none"), 40 | Raw(_) => write!(f, ""), 41 | IPFS(v) => { 42 | write!(f, "{}", String::from_utf8(v.clone()).unwrap()) 43 | } 44 | Hyper(_) => write!(f, ""), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /native/sdk/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use subxt::balances::*; 2 | use subxt::extrinsic::*; 3 | use subxt::sp_core; 4 | use subxt::sp_runtime::generic::Header; 5 | use subxt::sp_runtime::traits::{BlakeTwo256, IdentifyAccount, Verify}; 6 | use subxt::sp_runtime::{MultiSignature, OpaqueExtrinsic}; 7 | use subxt::system::*; 8 | 9 | use crate::pallet; 10 | 11 | /// an easy way to extract the balance type from `T` 12 | pub type BalanceOf = ::Balance; 13 | 14 | /// Alias to 512-bit hash when used in the context of a transaction signature on 15 | /// the chain. 16 | pub type Signature = MultiSignature; 17 | 18 | /// Some way of identifying an account on the chain. We intentionally make it 19 | /// equivalent to the public key of our transaction signing scheme. 20 | pub type AccountId = 21 | <::Signer as IdentifyAccount>::AccountId; 22 | 23 | /// Balance of an account. 24 | pub type Balance = u128; 25 | 26 | /// Index of a transaction in the chain. 27 | pub type Index = u32; 28 | 29 | /// A hash of some data used by the chain. 30 | pub type Hash = sp_core::H256; 31 | 32 | /// Subsocial Runtime. 33 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 34 | pub struct SubsocialRuntime; 35 | 36 | impl subxt::Runtime for SubsocialRuntime { 37 | type Extra = DefaultExtra; 38 | type Signature = Signature; 39 | } 40 | 41 | impl System for SubsocialRuntime { 42 | type AccountData = AccountData>; 43 | type AccountId = AccountId; 44 | type Address = AccountId; 45 | type BlockNumber = u32; 46 | type Extrinsic = OpaqueExtrinsic; 47 | type Hash = Hash; 48 | type Hashing = BlakeTwo256; 49 | type Header = Header; 50 | type Index = Index; 51 | } 52 | 53 | impl Balances for SubsocialRuntime { 54 | type Balance = Balance; 55 | } 56 | 57 | // implementations for Subsoical Pallets 58 | impl pallet::spaces::Spaces for SubsocialRuntime {} 59 | impl pallet::posts::Posts for SubsocialRuntime {} 60 | impl pallet::reactions::Reactions for SubsocialRuntime {} 61 | impl pallet::profiles::Profiles for SubsocialRuntime {} 62 | impl pallet::space_follows::SpaceFollows for SubsocialRuntime {} 63 | impl pallet::profile_follows::ProfileFollows for SubsocialRuntime {} 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | use crate::pallet::posts::*; 69 | use crate::pallet::reactions::*; 70 | use crate::pallet::spaces::*; 71 | 72 | type Error = Box; 73 | 74 | type SpaceById = SpaceByIdStore; 75 | type SpaceIdByHandle = SpaceIdByHandleStore; 76 | type PostById = PostByIdStore; 77 | type PostIdsBySpaceId = PostIdsBySpaceIdStore; 78 | type ReactionById = ReactionByIdStore; 79 | type ReactionIdsByPostId = ReactionIdsByPostIdStore; 80 | 81 | async fn get_client() -> subxt::Client { 82 | subxt::ClientBuilder::new() 83 | .set_url("wss://rpc.subsocial.network") 84 | .build() 85 | .await 86 | .unwrap() 87 | } 88 | 89 | #[async_std::test] 90 | async fn connection() { 91 | let client = get_client().await; 92 | let props = client.properties(); 93 | dbg!(props); 94 | } 95 | 96 | #[async_std::test] 97 | async fn space_by_id() -> Result<(), Error> { 98 | let client = get_client().await; 99 | let space_id = client 100 | .fetch(&SpaceIdByHandle::new("subsocial"), None) 101 | .await? 102 | .unwrap_or(1); // id 1 is the default for subsocial. 103 | let space = client.fetch(&SpaceById::new(space_id), None).await?; 104 | println!("{}", space.unwrap()); 105 | Ok(()) 106 | } 107 | 108 | #[async_std::test] 109 | async fn space_posts() -> Result<(), Error> { 110 | let client = get_client().await; 111 | let space_id = 1; 112 | let post_ids = client 113 | .fetch(&PostIdsBySpaceId::new(space_id), None) 114 | .await? 115 | .unwrap(); 116 | for post_id in post_ids { 117 | let storage = PostById::new(post_id); 118 | let post = client.fetch(&storage, None).await?.unwrap(); 119 | println!("{}", post); 120 | } 121 | Ok(()) 122 | } 123 | 124 | #[async_std::test] 125 | async fn post_reactions() -> Result<(), Error> { 126 | let client = get_client().await; 127 | let post_id = 1; 128 | let storage = ReactionIdsByPostId::new(post_id); 129 | let reaction_ids = client.fetch(&storage, None).await?.unwrap(); 130 | println!("Post {} Reactions:", post_id); 131 | for reaction_id in reaction_ids { 132 | let storage = ReactionById::new(reaction_id); 133 | let reaction = client.fetch(&storage, None).await?.unwrap(); 134 | println!("{}", reaction); 135 | } 136 | Ok(()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.0" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.6.1" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | cli_util: 40 | dependency: transitive 41 | description: 42 | name: cli_util 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "0.3.0" 46 | clock: 47 | dependency: transitive 48 | description: 49 | name: clock 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.0" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.15.0" 60 | dio: 61 | dependency: "direct main" 62 | description: 63 | name: dio 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "4.0.0" 67 | dio_http2_adapter: 68 | dependency: "direct main" 69 | description: 70 | name: dio_http2_adapter 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.0.0" 74 | fake_async: 75 | dependency: transitive 76 | description: 77 | name: fake_async 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.2.0" 81 | ffi: 82 | dependency: "direct main" 83 | description: 84 | name: ffi 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.1.2" 88 | ffigen: 89 | dependency: "direct dev" 90 | description: 91 | name: ffigen 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "3.0.0" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "6.1.0" 102 | fixnum: 103 | dependency: transitive 104 | description: 105 | name: fixnum 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.0.0" 109 | flutter: 110 | dependency: "direct main" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_test: 115 | dependency: "direct dev" 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | glob: 120 | dependency: transitive 121 | description: 122 | name: glob 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "2.0.1" 126 | http2: 127 | dependency: transitive 128 | description: 129 | name: http2 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.0.0" 133 | http_parser: 134 | dependency: transitive 135 | description: 136 | name: http_parser 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "4.0.0" 140 | lint: 141 | dependency: "direct dev" 142 | description: 143 | name: lint 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "1.5.3" 147 | logging: 148 | dependency: transitive 149 | description: 150 | name: logging 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.0.1" 154 | matcher: 155 | dependency: transitive 156 | description: 157 | name: matcher 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.12.10" 161 | meta: 162 | dependency: transitive 163 | description: 164 | name: meta 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.3.0" 168 | path: 169 | dependency: "direct main" 170 | description: 171 | name: path 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "1.8.0" 175 | path_provider: 176 | dependency: "direct main" 177 | description: 178 | name: path_provider 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "2.0.2" 182 | path_provider_linux: 183 | dependency: transitive 184 | description: 185 | name: path_provider_linux 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "2.0.0" 189 | path_provider_macos: 190 | dependency: transitive 191 | description: 192 | name: path_provider_macos 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "2.0.0" 196 | path_provider_platform_interface: 197 | dependency: transitive 198 | description: 199 | name: path_provider_platform_interface 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "2.0.1" 203 | path_provider_windows: 204 | dependency: transitive 205 | description: 206 | name: path_provider_windows 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "2.0.1" 210 | pedantic: 211 | dependency: transitive 212 | description: 213 | name: pedantic 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "1.11.0" 217 | platform: 218 | dependency: transitive 219 | description: 220 | name: platform 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "3.0.0" 224 | plugin_platform_interface: 225 | dependency: transitive 226 | description: 227 | name: plugin_platform_interface 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "2.0.1" 231 | process: 232 | dependency: transitive 233 | description: 234 | name: process 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "4.2.1" 238 | protobuf: 239 | dependency: "direct main" 240 | description: 241 | name: protobuf 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "2.0.0" 245 | quiver: 246 | dependency: transitive 247 | description: 248 | name: quiver 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "3.0.1" 252 | sky_engine: 253 | dependency: transitive 254 | description: flutter 255 | source: sdk 256 | version: "0.0.99" 257 | source_span: 258 | dependency: transitive 259 | description: 260 | name: source_span 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "1.8.1" 264 | stack_trace: 265 | dependency: transitive 266 | description: 267 | name: stack_trace 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "1.10.0" 271 | stream_channel: 272 | dependency: transitive 273 | description: 274 | name: stream_channel 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "2.1.0" 278 | string_scanner: 279 | dependency: transitive 280 | description: 281 | name: string_scanner 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.1.0" 285 | term_glyph: 286 | dependency: transitive 287 | description: 288 | name: term_glyph 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.2.0" 292 | test_api: 293 | dependency: transitive 294 | description: 295 | name: test_api 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "0.3.0" 299 | typed_data: 300 | dependency: transitive 301 | description: 302 | name: typed_data 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.3.0" 306 | vector_math: 307 | dependency: transitive 308 | description: 309 | name: vector_math 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.1.0" 313 | win32: 314 | dependency: transitive 315 | description: 316 | name: win32 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "2.2.5" 320 | xdg_directories: 321 | dependency: transitive 322 | description: 323 | name: xdg_directories 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "0.2.0" 327 | yaml: 328 | dependency: transitive 329 | description: 330 | name: yaml 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "3.1.0" 334 | sdks: 335 | dart: ">=2.13.0 <3.0.0" 336 | flutter: ">=1.20.0" 337 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: subsocial_sdk 2 | description: Flutter SDK for Subsocial. 3 | version: 0.1.0 4 | author: Shady Khalifa 5 | homepage: https://subsocial.network 6 | 7 | environment: 8 | sdk: ">=2.13.0 <3.0.0" 9 | flutter: ">=1.20.0" 10 | 11 | dependencies: 12 | dio: ^4.0.0 13 | dio_http2_adapter: ^2.0.0 14 | ffi: ^1.1.2 15 | flutter: 16 | sdk: flutter 17 | path: ^1.8.0 18 | path_provider: ^2.0.2 19 | protobuf: ^2.0.0 20 | 21 | dev_dependencies: 22 | ffigen: ^3.0.0 23 | flutter_test: 24 | sdk: flutter 25 | lint: ^1.5.3 26 | 27 | flutter: 28 | plugin: 29 | platforms: 30 | android: 31 | package: network.subsocial.subsocial_sdk 32 | pluginClass: SubsocialSdkPlugin 33 | ios: 34 | pluginClass: SubsocialSdkPlugin 35 | 36 | ffigen: 37 | output: 'lib/ffi.dart' 38 | name: 'RawSubsoical' 39 | description: 'Subscoial FFI Binding' 40 | sort: true 41 | comments: 42 | style: any 43 | length: full 44 | headers: 45 | entry-points: 46 | - 'native/ffi/binding.h' 47 | include-directives: 48 | - '**binding.h' 49 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "clippy"] 4 | targets = [ 5 | "aarch64-linux-android", 6 | "armv7-linux-androideabi", 7 | "x86_64-linux-android", 8 | "i686-linux-android", 9 | "aarch64-apple-ios", 10 | "x86_64-apple-ios", 11 | ] 12 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 4 3 | 4 | -------------------------------------------------------------------------------- /scripts/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | set -e 4 | cd ./example 5 | echo 'Building APK for release (arm64)...' 6 | flutter build apk --release \ 7 | --target-platform android-arm64 \ 8 | --dart-define=protobuf.omit_message_names=true 9 | cd .. 10 | -------------------------------------------------------------------------------- /scripts/protogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | SRC_DIR=./native/ffi 4 | DST_DIR=./lib/generated 5 | 6 | mkdir -p $DST_DIR 7 | 8 | echo "Checking for protoc dart plugin..." 9 | 10 | dart pub global activate protoc_plugin 11 | 12 | echo 'Generating new definitions...' 13 | 14 | protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/*.proto 15 | 16 | echo 'Generated ...OK' 17 | 18 | flutter format $DST_DIR 19 | 20 | echo "Done!" 21 | -------------------------------------------------------------------------------- /test/ipfs_client_test.dart: -------------------------------------------------------------------------------- 1 | @Skip("This test is making an actual http request") 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:subsocial_sdk/subsocial_sdk.dart'; 4 | 5 | void main() { 6 | test("test querying multiple cids", () async { 7 | final expectedResponse = { 8 | "bafyreieqtfresp7eoiri64n5desneolzzktf5rgfbuf4aouf5y7ztzv4wm": { 9 | "body": "" 10 | }, 11 | "bafyreifthuzrz3i4gt2to7nfy4bzbi7ju32eahqzsnemb7nnedxhepzjg4": { 12 | "body": 13 | "Moonbeam recently shared more details about “Take Flight”. This is a token event for Moonbeam community giving them a chance to obtain Glimmer (\$GLMR) tokens - Moonbeam's utility tokens.\n\nTake Flight event includes 3 sequential rounds and for all of them will require a whitelist to participate. The first round will be Community Early Access, the second round will be Community General and the last one will be Future Community.\n\n80,000,000 \$GLMR will be allocated for the event and the event will start on September 7 (registration & identity verification will start sooner!)\n\nApart from Take Flight, Moonbeam Foundation will also run Liquidity Incentive Programs on both Moonriver and Moonbeam to allocate a percentage of the Moonbeam network to reward Moonbeam Crowdloan contributors.\n\nFor whitelist process, you can check this step-by-step guide for more information:\nhttps://moonbeam.foundation/tutorials/whitelist-telegram/ \n\nFor the full article, check: https://moonbeam.foundation/take-flight/", 14 | "canonical": 15 | "https://twitter.com/PolkaWarriors_/status/1426418825189236737", 16 | "image": "QmPKRsbj1B7E4o6t82TetGuzTtxB18RJfdhP6nYnfDmm6b", 17 | "tags": [ 18 | "Moonbeam", 19 | "Moonriver", 20 | "Take Flight", 21 | "GLMR", 22 | "Polkadot Ecosystem", 23 | "PolkaWarriors" 24 | ], 25 | "title": "Moonbeam Take Flight: A Community Token Event" 26 | }, 27 | "bafyreicfcv73pkxpnmri3ldhctjji25sxfzcihvqlm5gxifjpdo7fcnog4": { 28 | "body": 29 | "We are very excited to announce the release of [Sub.ID](https://sub.id/), a complimentary service to Subsocial, and our gift to the Substrate ecosystem!\n\nAre you tired of having to slowly switch chains in the polkadot.js wallet to retrieve your addresses? Have you ever spent a day out at the park where you can't access the polkadot.js wallet, and you need your address for a specific chain, but don't have it saved in your phone? We've got you covered! With SubID, all you need to do is bookmark one page in your browser, and you can access your various Substrate wallet addresses from anywhere.\n\nAs an example of what you can see on Sub ID, check the account of Shawn: https://sub.id/#/12hAtDZJGt4of3m2GqZcUCVAjZPALfvPwvtUTFZPQUbdX1Ud\n\nSubID's uses go beyond just personal convenience. It will also aid in tipping people, as you can now easily tip someone in any token you want, without having to ask them for their address for that chain (as all you need to know is their address on any Substrate chain). In the future we will be adding a \"Send\" button to the SubID interface to further facilitate actions like this.\n\nHere's how it works: Your different addresses for all the different Substrate chains are essentially linked together, and your Polkadot address can be used to derive your Subsocial address, and your Kusama address, etc., and vice versa (the only exception is Moonriver/Moonbeam addresses — there is no way to convert them into the Substrate address format in order to derive the other addresses). Your address for any Substrate-based blockchain can be used to derive your address for every other Substrate-based blockchain. Pretty neat, huh? SubID leverages this to create a page that is able to show all the addresses of one account, just based off of a single address from any Substrate chain.\n\n## An Overview\n\n![](https://miro.medium.com/max/1400/1*9YIDk3IIe706cEdbsaS2OQ.png)\n\nThis section summarizes your account, using the name, profile picture, and description you have set on Subsocial, as well as displaying your Substrate address, and a rough estimate of the total value of all your Substrate tokens in terms of \$USD. In addition, there is a Follow button that will take you to their [Polkaverse](https://polkaverse.com/) page!\n\n![](https://miro.medium.com/max/1400/1*5AHzB0K3pELl4Kfelw0F5A.png)\n\nThis section shows your address and token balance for various chains. Our development team has to add these manually, but we will eventually get all chains listed. The price column pulls the price from CoinGecko, and the last column shows the total \$USD value of all your tokens. There is an option to hide chains that you do not possess any tokens for, as well as the ability to switch between the list view (shown above) and a grid view, depending on personal preference.\n\n![](https://miro.medium.com/max/1400/1*AXwYMQphOoI-iez8v0AHyw.png)\n\nThis section simply pulls the data from your on-chain identity, if you have set one through the polkadot.js wallet. It will list every chain that you have set an identity on, and if you have received a judgement to have your identity verified, there will be a green circle around the chain's icon.\n\nWe hope the community will find this tool useful! A reminder to join us on [Twitter](https://twitter.com/SubsocialChain), [Discord](https://discord.com/invite/w2Rqy2M), and [Telegram](https://t.me/Subsocial). We have also just added an email form to the front page of our [website](https://subsocial.network) if you would like to join our mailing list.\n\nHappy Subbing!", 30 | "image": "QmW3HPeaQVs8bbYRQiGghx8mrPVUL3VBrwGxdmZVmRvYQU", 31 | "tags": ["Sub ID", "Substrate Balances"], 32 | "title": 33 | "Introducing Sub ID: The One Stop Shop For All Your Substrate Addresses And Balances" 34 | } 35 | }; 36 | 37 | final cids = expectedResponse.entries.map((e) => e.key).toList(); 38 | 39 | final client = IpfsClient(); 40 | 41 | final response = await client.query(cids, (j) => j); 42 | 43 | expect(response, expectedResponse); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /test/json_models_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:subsocial_sdk/subsocial_sdk.dart'; 3 | 4 | void main() { 5 | group("PostMetadata", () { 6 | final jsonPosts = [ 7 | { 8 | "body": "\n\n> Example body.34234_", 9 | "tags": [], 10 | "title": "Polkadot Digest 27 Aug 2021" 11 | }, 12 | { 13 | "body": "\n\n> Example3434 body.34234_", 14 | "link": "https://www.youtube.com/watch?v=je09G7lmvQs", 15 | "tags": [ 16 | "Subsocial", 17 | "Kusama", 18 | "Polkadot", 19 | "KSM", 20 | "DOT", 21 | "Social Media" 22 | ] 23 | }, 24 | { 25 | "body": "\n\n> Polkadot Updates\n> -------ns-on-kusama/).ls.com)._", 26 | "image": "QmYDGqZBZ9NUbeccsoBg6iXryS3kutdJgxTYSRRHTiZYTG", 27 | "tags": [ 28 | "polkadot", 29 | "kusama", 30 | "acala", 31 | "shiden", 32 | "moonbeam", 33 | "dotsama" 34 | ], 35 | "title": "DotLeap 52: Parachain Auctions, Round 2" 36 | }, 37 | { 38 | "body": "\n\n> Example body._", 39 | "canonical": 40 | "https://twitter.com/PolkaWarriors_/status/1430411023446540288", 41 | "image": "QmVDE7VcLhDkfT6pvyLbiF55WTkCeCRhjXcSFNaNykcFzf", 42 | "tags": [ 43 | "MOVR", 44 | "Moonriver", 45 | "Moonbeam", 46 | "Metamask", 47 | "Polkadot Ecosystem" 48 | ], 49 | "title": "How to add Moonriver to Metamask" 50 | } 51 | ]; 52 | 53 | for (var i = 0; i < jsonPosts.length; i++) { 54 | final jsonPost = jsonPosts[i]; 55 | test("test #$i", () { 56 | final post = PostMetadata.fromJson(jsonPost); 57 | expect(post.body, jsonPost['body']); 58 | expect(post.image, jsonPost['image']); 59 | expect(post.tags, jsonPost['tags']); 60 | expect(post.title, jsonPost['title']); 61 | expect(post.link, jsonPost['link']); 62 | expect(post.canonical, jsonPost['canonical']); 63 | }); 64 | } 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /test/subsocial_sdk_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:subsocial_sdk/subsocial_sdk.dart'; 3 | 4 | void main() { 5 | test('Get Space with Id', () async { 6 | final sdk = await Subsocial.instance; 7 | final space = await sdk.spaceById(1); 8 | expect(space.handle, "subsocial"); 9 | }); 10 | 11 | test('Get Space with handle', () async { 12 | final sdk = await Subsocial.instance; 13 | final space = await sdk.spaceByHandle("subsocial"); 14 | final _ = await IpfsClient().query( 15 | [space.content.ipfs], 16 | (json) => SpaceMetadata.fromJson(json), 17 | ); 18 | expect(space.id.toInt(), 1); 19 | }); 20 | 21 | test('Get SpaceIds By Owner', () async { 22 | final sdk = await Subsocial.instance; 23 | final space = await sdk.spaceById(1); 24 | expect(space.hasOwner(), true); 25 | expect(space.handle, "subsocial"); 26 | final spaceIds = await sdk.spaceIdsByOwner(space.owner); 27 | expect(spaceIds.contains(space.id.toInt()), true); 28 | }); 29 | 30 | test('Get Space Posts', () async { 31 | final sdk = await Subsocial.instance; 32 | final space = await sdk.spaceByHandle("subsocial"); 33 | final postIds = await sdk.postsIdsBySpaceId(space.id.toInt()); 34 | final first5Posts = postIds.take(5); 35 | for (final postId in first5Posts) { 36 | final post = await sdk.postById(postId.toInt()); 37 | final _ = await IpfsClient().query( 38 | [post.content.ipfs], 39 | (json) => PostMetadata.fromJson(json), 40 | ); 41 | expect(post.spaceId, space.id); 42 | } 43 | }); 44 | 45 | test('Get Post Reactions', () async { 46 | final sdk = await Subsocial.instance; 47 | final space = await sdk.spaceByHandle("subsocial"); 48 | final postIds = await sdk.postsIdsBySpaceId(space.id.toInt()); 49 | final firstPostId = postIds.first; 50 | final reactionIds = await sdk.reactionIdsByPostId(firstPostId); 51 | final first5Reactions = reactionIds.take(5); 52 | for (final reactionId in first5Reactions) { 53 | final _ = await sdk.reactionById(reactionId.toInt()); 54 | } 55 | }); 56 | 57 | test('Get Post Replies', () async { 58 | final sdk = await Subsocial.instance; 59 | final space = await sdk.spaceByHandle("subsocial"); 60 | final postIds = await sdk.postsIdsBySpaceId(space.id.toInt()); 61 | final firstPostId = postIds.first; 62 | final repliesIds = await sdk.replyIdsByPostId(firstPostId); 63 | final first5Replies = repliesIds.take(5); 64 | for (final replyId in first5Replies) { 65 | final reply = await sdk.postById(replyId.toInt()); 66 | expect(reply.hasExtensionValue(), true); 67 | expect(reply.extensionValue.hasComment(), true); 68 | expect(reply.extensionValue.comment.rootPostId.toInt(), firstPostId); 69 | } 70 | }); 71 | 72 | test('Get Social Accounts', () async { 73 | final sdk = await Subsocial.instance; 74 | final space = await sdk.spaceByHandle("subsocial"); 75 | final owner = await sdk.socialAccountByAccountId(space.owner); 76 | expect(owner.hasProfile(), true); 77 | expect(owner.profile.hasContent(), true); 78 | }); 79 | 80 | test('Next {Space,Post}Id', () async { 81 | final sdk = await Subsocial.instance; 82 | final nextSpaceId = await sdk.nextSpaceId(); 83 | final nextPostId = await sdk.nextPostId(); 84 | expect(nextSpaceId > 0, true); 85 | expect(nextPostId > 0, true); 86 | }); 87 | 88 | test('Get Space Followers', () async { 89 | final sdk = await Subsocial.instance; 90 | final space = await sdk.spaceByHandle("subsocial"); 91 | final spaceFollowers = await sdk.spaceFollowers(space.id.toInt()); 92 | expect(spaceFollowers.length, space.followersCount); 93 | }); 94 | 95 | test('Get Spaces Followed by AccountId', () async { 96 | final sdk = await Subsocial.instance; 97 | final space = await sdk.spaceByHandle("subsocial"); 98 | final followedSpaces = await sdk.spacesFollowedByAccount(space.owner); 99 | expect(followedSpaces.contains(space.id.toInt()), true); 100 | }); 101 | 102 | test('Generate Account', () async { 103 | final sdk = await Subsocial.instance; 104 | final account = await sdk.generateAccount(); 105 | expect(account.hasPublicKey(), true); 106 | expect(account.hasSeedPhrase(), true); 107 | }); 108 | 109 | test('Import Account', () async { 110 | final sdk = await Subsocial.instance; 111 | final account = await sdk.importAccount(suri: '//Alice'); 112 | expect(account.hasPublicKey(), true); 113 | 114 | // generate and import. 115 | final generated = await sdk.generateAccount(); 116 | final imported = await sdk.importAccount(suri: generated.seedPhrase); 117 | expect(generated.publicKey, imported.publicKey); 118 | }); 119 | } 120 | --------------------------------------------------------------------------------