code files

This commit is contained in:
ggurdin 2023-11-08 12:27:18 -05:00
parent f3798f3ee8
commit f86aafcb74
536 changed files with 83522 additions and 2771 deletions

16
.env Normal file
View file

@ -0,0 +1,16 @@
BASE_API='https://api.staging.pangea.chat/api/v1'
CHOREO_API = 'https://api.staging.pangea.chat/choreo'
FRONTEND_URL='https://app.staging.pangea.chat'
SYNAPSE_URL = 'matrix.staging.pangea.chat'
CHOREO_API_KEY = 'e6fa9fa97031ba0c852efe78457922f278a2fbc109752fe18e465337699e9873'
GOOGLE_AUTH_KEY = '466850640825-qegdiq3mpj3h5e0e79ud5hnnq2c22mi3.apps.googleusercontent.com'
RC_PROJECT = 'a499dc21'
RC_KEY = 'sk_eVGBdPyInaOfJrKlPBgFVnRynqKJB'
RC_GOOGLE_KEY = 'goog_paQMrzFKGzuWZvcMTPkkvIsifJe'
RC_IOS_KEY = 'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv'
RC_STRIPE_KEY = 'strp_YWZxWUeEfvagiefDNoofinaRCOl'
RC_OFFERING_NAME = 'test'
STRIPE_MANAGEMENT_LINK = 'https://billing.stripe.com/p/login/test_9AQaI8d3O9lmaXe5kk'

16
.env.prod Normal file
View file

@ -0,0 +1,16 @@
BASE_API='https://api.pangea.chat/api/v1'
CHOREO_API = 'https://api.pangea.chat/choreo'
FRONTEND_URL='https://app.pangea.chat'
SYNAPSE_URL = 'matrix.pangea.chat'
CHOREO_API_KEY = '223d863480cbb439fd9f16538c5b56ea09ac375c5aa7ea4c39cc32211117afae'
GOOGLE_AUTH_KEY = '723169076587-tq82s1qrugqphsl9527tng43tbuc7mv1.apps.googleusercontent.com'
RC_PROJECT = 'a499dc21'
RC_KEY = 'sk_eVGBdPyInaOfJrKlPBgFVnRynqKJB'
RC_GOOGLE_KEY = 'goog_paQMrzFKGzuWZvcMTPkkvIsifJe'
RC_IOS_KEY = 'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv'
RC_STRIPE_KEY = 'strp_YWZxWUeEfvagiefDNoofinaRCOl'
RC_OFFERING_NAME = 'default'
STRIPE_MANAGEMENT_LINK = 'https://billing.stripe.com/p/login/dR6dSkf5p6rBc4EcMM'

6
.gitignore vendored
View file

@ -36,6 +36,9 @@ prime
.pub/
/build/
# Gitlab runner locally
/builds/
# Web related
docs/tailwind.css
@ -53,6 +56,7 @@ lib/l10n_old
ios/Flutter/.last_build_id
ios/Podfile.lock
ios/Runner.ipa
scripts/.credentials
/windows/out
/winuwp/out
@ -60,3 +64,5 @@ ios/Runner.ipa
/macos/out
.vs
olm
needed-translations.txt

View file

@ -1,7 +1,7 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
@ -15,9 +15,24 @@ migration:
- platform: root
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: android
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: ios
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: linux
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: macos
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: web
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: windows
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
# User provided section

71
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,71 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "pangea-chat",
"request": "launch",
"type": "dart",
// "args": [
// "-d",
// "chrome",
// "--web-port",
// "49632"
// ],
},
{
"name": "pangea-chat (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "pangea-chat (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "pangea_choreographer",
"cwd": "pangea_packages\\pangea_choreographer",
"request": "launch",
"type": "dart"
},
{
"name": "pangea_choreographer (profile mode)",
"cwd": "pangea_packages\\pangea_choreographer",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "pangea_choreographer (release mode)",
"cwd": "pangea_packages\\pangea_choreographer",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "pangea_language",
"cwd": "pangea_packages\\pangea_language",
"request": "launch",
"type": "dart"
},
{
"name": "pangea_language (profile mode)",
"cwd": "pangea_packages\\pangea_language",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "pangea_language (release mode)",
"cwd": "pangea_packages\\pangea_language",
"request": "launch",
"type": "dart",
"flutterMode": "release"
}
]
}

9
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"dart.previewLsp": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true,
"source.sortMembers": false
},
"editor.formatOnSave": true
}

35
TRANSLATORS_GUIDE.md Normal file
View file

@ -0,0 +1,35 @@
# Translators Guide
There are 3 main types of strings to be translated.
## Simple
```
Add new friend
```
They are just plain text and are to be translated in full.
## Placeholder
```
{username} changed their avatar
```
Contains one or more words surrounded by curly brackets "`{}`". Anything outside of the curly brackets is to be translated as normal, but the words in the curly brackets are **NOT** to be translated. In the above example "`{username}`" will be replaced by the users actual username by FluffyChat.
## Plural
- {count,plural, =1{**1 more event**} other{{count} **more events**}}
This is the most complicated string type, the parts in bold are the only parts that need translating in this string. You can identify plural strings by seeing the pattern `{word,plural,` at the start. `=1` and `other` are "selectors" so you can have multiple different translations for different quantities. `other` is the only required selector and will be chosen if the count does not match any other selectors.
Selector | Matches
---|---
=0 | a count of exactly 0
=1 | a count of exactly 1
=2 | a count of exactly 2
other | any number unless it matches a more specific rule
There is also "few" and "many", but they seem to have language specific meaning.
Also the selectors do not need to match the English version such as your language may not even use different words for when there is more than one of something so:
- {count,plural, other{{count} \<insert translation here\>}}
could be a perfectly resonable way to translate.

View file

@ -22,6 +22,7 @@ if (flutterVersionName == null) {
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
@ -43,7 +44,7 @@ android {
}
defaultConfig {
applicationId "chat.fluffy.fluffychat"
applicationId "com.talktolearn.chat"
minSdkVersion 19
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()

View file

@ -1,40 +1,40 @@
{
"project_info": {
"project_number": "865731724731",
"project_id": "fluffychat-ef3e8",
"storage_bucket": "fluffychat-ef3e8.appspot.com"
"project_number": "545984292675",
"project_id": "pangea-chat-936ee",
"storage_bucket": "pangea-chat-936ee.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:865731724731:android:ec427b3b1dcd4a1e64309e",
"mobilesdk_app_id": "1:545984292675:android:d808acce7a80c20bb931f6",
"android_client_info": {
"package_name": "chat.fluffy.fluffychat"
"package_name": "com.talktolearn.chat"
}
},
"oauth_client": [
{
"client_id": "865731724731-od6969v178ul9970elgacpt936v5t7qg.apps.googleusercontent.com",
"client_id": "545984292675-2amsnoan1mt6lec1fld1a7eagu6gej7o.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBLdZpGSPjcinikB4lAU6awW_h88NG17Sg"
"current_key": "AIzaSyAyWBbl83WXzbVr6txyCmlUsZhpWomQfdg"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "865731724731-od6969v178ul9970elgacpt936v5t7qg.apps.googleusercontent.com",
"client_id": "545984292675-2amsnoan1mt6lec1fld1a7eagu6gej7o.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "865731724731-ofdr7e6m04murgb1bvchlj9oaos0q5i3.apps.googleusercontent.com",
"client_id": "545984292675-f5p76l3h9sibsonrct7a8l9ca3c69at0.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "im.fluffychat.app"
"bundle_id": "com.talktolearn.chat"
}
}
]

View file

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

View file

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="chat.fluffy.fluffychat" android:installLocation="auto">
package="com.talktolearn.chat" android:installLocation="auto">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
@ -27,11 +27,15 @@
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- #Pangea -->
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Pangea# -->
<uses-sdk
tools:overrideLibrary="io.wazo.callkeep, net.touchcapture.qr.flutterqr, com.cloudwebrtc.webrtc, org.webrtc, com.it_nomads.fluttersecurestorage, com.pichillilorenzo.flutter_inappwebview, com.example.video_compress, com.otaliastudios.transcoder, com.otaliastudios.opengl, com.kineapps.flutter_file_dialog, com.llfbandit.record, com.pravera.flutter_foreground_task"/>
<application
android:label="FluffyChat"
android:label="Pangea Chat"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:allowBackup="false"
@ -73,7 +77,7 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="im.fluffychat" android:host="chat" />
<data android:scheme="com.talktolearn.chat" android:host="chat" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
@ -104,16 +108,18 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="im.fluffychat" android:host="login"/>
<data android:scheme="matrix.pangea.chat" android:host="login"/>
</intent-filter>
</activity>
<service android:name=".FcmPushService"
<!-- #Pangea This prevents Android notifications from coming through -->
<!-- <service android:name=".FcmPushService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
</service> -->
<!-- Pangea# -->
<service android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="camera|microphone|mediaProjection">

View file

@ -0,0 +1,36 @@
/*package com.talktolearn.chat
import com.famedly.fcm_shared_isolate.FcmSharedIsolateService
import com.talktolearn.chat.MainActivity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.view.FlutterMain
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
class FcmPushService : FcmSharedIsolateService() {
override fun getEngine(): FlutterEngine {
return provideEngine(getApplicationContext())
}
companion object {
fun provideEngine(context: Context): FlutterEngine {
var engine = MainActivity.engine
if (engine == null) {
engine = MainActivity.provideEngine(context)
engine.getLocalizationPlugin().sendLocalesToFlutter(
context.getResources().getConfiguration())
engine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault())
}
return engine
}
}
}
*/

View file

@ -0,0 +1,33 @@
package com.talktolearn.chat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import android.content.Context
import androidx.multidex.MultiDex
class MainActivity : FlutterActivity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return provideEngine(this)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// do nothing, because the engine was been configured in provideEngine
}
companion object {
var engine: FlutterEngine? = null
fun provideEngine(context: Context): FlutterEngine {
val eng = engine ?: FlutterEngine(context, emptyArray(), true, false)
engine = eng
return eng
}
}
}

View file

@ -0,0 +1,23 @@
package com.talktolearn.chat
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import org.unifiedpush.flutter.connector.UnifiedPushReceiver
import android.content.Context
class UnifiedPushReceiver : UnifiedPushReceiver() {
override fun getEngine(context: Context): FlutterEngine {
var engine = MainActivity.engine
if (engine == null) {
engine = MainActivity.provideEngine(context)
engine.localizationPlugin.sendLocalesToFlutter(
context.resources.configuration
)
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
}
return engine
}
}

View file

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

View file

@ -8,7 +8,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
//classpath 'com.google.gms:google-services:4.3.8'
classpath 'com.google.gms:google-services:4.3.13'
}
}

View file

@ -1,2 +1,2 @@
json_key_file("keys.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("chat.fluffy.fluffychat") # e.g. com.krausefx.app
package_name("com.talktolearn.chat") # e.g. com.krausefx.app

View file

@ -0,0 +1 @@
Check out https://gitlab.com/ChristianPauly/fluffychat-flutter/-/blob/main/CHANGELOG.md for the changelog.

0
appimage/AppRun Executable file → Normal file
View file

BIN
assets/colors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/encryption.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

4
assets/pangea/apple.svg Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="842.32007" height="1000.0001">
<path fill="#fff" d="M824.66636 779.30363c-15.12299 34.93724-33.02368 67.09674-53.7638 96.66374-28.27076 40.3074-51.4182 68.2078-69.25717 83.7012-27.65347 25.4313-57.2822 38.4556-89.00964 39.1963-22.77708 0-50.24539-6.4813-82.21973-19.629-32.07926-13.0861-61.55985-19.5673-88.51583-19.5673-28.27075 0-58.59083 6.4812-91.02193 19.5673-32.48053 13.1477-58.64639 19.9994-78.65196 20.6784-30.42501 1.29623-60.75123-12.0985-91.02193-40.2457-19.32039-16.8514-43.48632-45.7394-72.43607-86.6641-31.060778-43.7024-56.597041-94.37983-76.602609-152.15586C10.740416 658.44309 0 598.01283 0 539.50845c0-67.01648 14.481044-124.8172 43.486336-173.25401C66.28194 327.34823 96.60818 296.6578 134.5638 274.1276c37.95566-22.53016 78.96676-34.01129 123.1321-34.74585 24.16591 0 55.85633 7.47508 95.23784 22.166 39.27042 14.74029 64.48571 22.21538 75.54091 22.21538 8.26518 0 36.27668-8.7405 83.7629-26.16587 44.90607-16.16001 82.80614-22.85118 113.85458-20.21546 84.13326 6.78992 147.34122 39.95559 189.37699 99.70686-75.24463 45.59122-112.46573 109.4473-111.72502 191.36456.67899 63.8067 23.82643 116.90384 69.31888 159.06309 20.61664 19.56727 43.64066 34.69027 69.2571 45.4307-5.55531 16.11062-11.41933 31.54225-17.65372 46.35662zM631.70926 20.0057c0 50.01141-18.27108 96.70693-54.6897 139.92782-43.94932 51.38118-97.10817 81.07162-154.75459 76.38659-.73454-5.99983-1.16045-12.31444-1.16045-18.95003 0-48.01091 20.9006-99.39207 58.01678-141.40314 18.53027-21.27094 42.09746-38.95744 70.67685-53.0663C578.3158 9.00229 605.2903 1.31621 630.65988 0c.74076 6.68575 1.04938 13.37191 1.04938 20.00505z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

6
assets/pangea/google.svg Normal file
View file

@ -0,0 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.0001 24.5455C48.0001 22.8437 47.8442 21.2074 47.5548 19.6364H24.4898V28.9201H37.6698C37.1021 31.9201 35.3767 34.4619 32.783 36.1637V42.1856H40.6977C45.3285 38.0074 48.0001 31.8546 48.0001 24.5455Z" fill="#4285F4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.4899 47.9999C31.1021 47.9999 36.6457 45.851 40.6977 42.1856L32.783 36.1637C30.59 37.6037 27.7849 38.4545 24.4899 38.4545C18.1114 38.4545 12.7125 34.2327 10.7867 28.5599H2.60484V34.7781C6.63454 42.6218 14.9166 47.9999 24.4899 47.9999Z" fill="#34A853"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7867 28.5599C10.2969 27.1199 10.0186 25.5819 10.0186 24.0001C10.0186 22.4183 10.2969 20.8801 10.7867 19.4401V13.2219H2.60483C0.946199 16.4619 0 20.1273 0 24.0001C0 27.8728 0.94621 31.5381 2.60484 34.7781L10.7867 28.5599Z" fill="#FBBC05"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.4899 9.5454C28.0854 9.5454 31.3136 10.7563 33.8517 13.1345L40.8758 6.25085C36.6346 2.37812 31.091 -6.10352e-05 24.4899 -6.10352e-05C14.9166 -6.10352e-05 6.63452 5.37825 2.60483 13.2219L10.7867 19.4401C12.7125 13.7673 18.1114 9.5454 24.4899 9.5454Z" fill="#EA4335"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/pangea/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,5 @@
<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M71.9441 13.7644L96.886 48.1091C96.9531 48.2015 96.949 48.3277 96.8761 48.4156L87.8737 59.2707C87.7711 59.3944 87.8103 59.5815 87.9539 59.6536L93.8463 62.6141C94.011 62.6968 94.0326 62.9231 93.8866 63.0356L58.3607 90.3882C58.317 90.4219 58.2634 90.4401 58.2082 90.4401H44.2072C44.03 90.4401 43.909 90.2608 43.9754 90.0965L62.8627 43.3445C62.9146 43.216 62.8521 43.0699 62.7234 43.0186L36.4149 32.5355C36.2163 32.4564 36.2022 32.1806 36.3918 32.0817L71.6261 13.6897C71.736 13.6323 71.8713 13.6641 71.9441 13.7644Z" fill="white"/>
<path d="M48.4415 48.4923L36.1145 96.2033C36.0527 96.4426 35.7182 96.4567 35.6365 96.2235L24.301 63.8751C24.2697 63.7859 24.1908 63.7219 24.097 63.7098L6.19491 61.4036C6.11216 61.3929 6.04019 61.3417 6.00303 61.267L0.0814505 49.3666C0.032944 49.2692 0.0531086 49.1515 0.131305 49.0757L12.949 36.6561C13.0158 36.5914 13.1131 36.5691 13.2014 36.5983L48.2779 48.1923C48.4029 48.2337 48.4744 48.3648 48.4415 48.4923Z" fill="white"/>
<path d="M10.7145 15.1023L8.90488 32.3767C8.88496 32.5668 9.07717 32.7081 9.25268 32.6322L55.3852 12.6994C55.4546 12.6694 55.5067 12.6096 55.527 12.5368L56.9484 7.41574C56.9725 7.329 57.0413 7.2619 57.1287 7.24007L70.1057 3.9958C70.3789 3.9275 70.3482 3.52978 70.0677 3.50429L45.28 1.25085C45.0914 1.23371 44.953 1.42427 45.0275 1.59831L48.3112 9.26009C48.3908 9.44579 48.2286 9.6443 48.0308 9.60338L24.3614 4.70886C24.2905 4.69421 24.2167 4.71099 24.1592 4.75486L10.8116 14.9295C10.7568 14.9713 10.7217 15.0338 10.7145 15.1023Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/start_chat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

9
docs/en/privacy.html Normal file
View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="7; url='https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md'" />
</head>
<body>
<p>Please follow <a href="https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md">this link</a>.</p>
</body>
</html>

39
docs/how_to_fork.md Normal file
View file

@ -0,0 +1,39 @@
# How to create your own FluffyChat fork
## 1. License
FluffyChat is licensed under AGPL. Read the license
(https://gitlab.com/ChristianPauly/fluffychat-flutter/-/blob/main/LICENSE) and
make sure that your fork is open source under the same license and that you
fulfill all requirements. Maybe you should consider contacting a lawyer **before**
you publish your fork.
## 2. Disable end-to-end encryption!
Due to US export regulations you are not allowed to publish your app in
a store or anywhere on a US server before you have removed everything regarding
the encryption or fulfill the regulations.
Learn more:
https://www.bis.doc.gov/index.php/policy-guidance/encryption
If you need help from us with using E2EE in your fork read more below under the
topic "**Official Support**".
## 3. Stay up to date!
FluffyChat contains security related stuff. If we find a security bug, we will
try to fix it as soon as possible and ship it with a new version. But this
means that your fork is out of date and a security risk. You can't be awake
24 hours a day so you must decide how you protect your users by chosing one
of the following methods:
1. Make your fork as minimal as possible and enable repository mirroring. Set
up a CI which publishes new versions automatically if FluffyChat publishes a
bug fix.
2. Never sleep and pay a big team where one guy at least is never sleeping.
3. Contact [famedly.com](https://famedly.com) to buy official support.
## 4. Official Support
FluffyChat is free as in free speech and not free beer! Please contact
my company [famedly.com](https://famedly.com) for offers and official support
and take in mind that it costs a lot of work and time to maintain FluffyChat
or the Famedly Matrix SDK. So we can't give you support for free. So please
expect around 1$ per month per user of your fork.

View file

@ -116,4 +116,4 @@
</body>
</html>
</html>

1
docs/tailwind.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
./android/fastlane

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -475,7 +475,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -491,7 +491,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.32.1;
PRODUCT_BUNDLE_IDENTIFIER = im.fluffychat.app;
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -615,7 +615,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -631,7 +631,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.32.1;
PRODUCT_BUNDLE_IDENTIFIER = im.fluffychat.app;
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -649,7 +649,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -665,7 +665,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.32.1;
PRODUCT_BUNDLE_IDENTIFIER = im.fluffychat.app;
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -685,7 +685,7 @@
CODE_SIGN_ENTITLEMENTS = "FluffyChat Share/FluffyChat Share.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "FluffyChat Share/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
@ -697,7 +697,7 @@
MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "im.fluffychat.app.FluffyChat-Share";
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat.FluffyChatShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -719,7 +719,7 @@
CODE_SIGN_ENTITLEMENTS = "FluffyChat Share/FluffyChat Share.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "FluffyChat Share/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
@ -730,7 +730,7 @@
);
MARKETING_VERSION = 1.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "im.fluffychat.app.FluffyChat-Share";
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat.FluffyChatShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
@ -750,7 +750,7 @@
CODE_SIGN_ENTITLEMENTS = "FluffyChat Share/FluffyChat Share.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4NXF6Z997G;
DEVELOPMENT_TEAM = PJ8L5H7L7H;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "FluffyChat Share/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
@ -761,7 +761,7 @@
);
MARKETING_VERSION = 1.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "im.fluffychat.app.FluffyChat-Share";
PRODUCT_BUNDLE_IDENTIFIER = com.talktolearn.chat.FluffyChatShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -3,21 +3,21 @@
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>865731724731-ofdr7e6m04murgb1bvchlj9oaos0q5i3.apps.googleusercontent.com</string>
<string>545984292675-f5p76l3h9sibsonrct7a8l9ca3c69at0.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.865731724731-ofdr7e6m04murgb1bvchlj9oaos0q5i3</string>
<string>com.googleusercontent.apps.545984292675-f5p76l3h9sibsonrct7a8l9ca3c69at0</string>
<key>API_KEY</key>
<string>AIzaSyA8ZUBcuny0HjPwF2Q2fvDyQTC5dG2VHlE</string>
<string>AIzaSyCl8QZd9_PnaqJY2zLHCwlsmSWdq7hnH-U</string>
<key>GCM_SENDER_ID</key>
<string>865731724731</string>
<string>545984292675</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>im.fluffychat.app</string>
<string>com.talktolearn.chat</string>
<key>PROJECT_ID</key>
<string>fluffychat-ef3e8</string>
<string>pangea-chat-936ee</string>
<key>STORAGE_BUCKET</key>
<string>fluffychat-ef3e8.appspot.com</string>
<string>pangea-chat-936ee.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
@ -29,6 +29,6 @@
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:865731724731:ios:79fd983ce46cb40c64309e</string>
<string>1:545984292675:ios:1226406ecc36e056b931f6</string>
</dict>
</plist>

View file

@ -9,7 +9,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>FluffyChat</string>
<string>Pangea Chat</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -17,7 +17,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>fluffychat</string>
<string>pangeachat</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@ -52,27 +52,29 @@
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Play audio and voice messages on bluetooth devices</string>
<key>NSCalendarsUsageDescription</key>
<string>Share calendar dates with your contacts in FluffyChat.</string>
<string>Share calendar dates with your contacts in Pangea Chat.</string>
<key>NSCameraUsageDescription</key>
<string>Open the camera and take a picture to share them with your contacts on FluffyChat.</string>
<string>Open the camera and take a picture to share them with your contacts on Pangea Chat.</string>
<key>NSContactsUsageDescription</key>
<string>Share contacts with your contacts in FluffyChat.</string>
<string>Share contacts with your contacts in Pangea Chat.</string>
<key>NSFaceIDUsageDescription</key>
<string>FluffyChat uses an app lock for an additional security level</string>
<string>Pangea Chat uses an app lock for an additional security level</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Share your location with your contacts in FluffyChat.</string>
<string>Share your location with your contacts in Pangea Chat.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Share your location with your contacts in FluffyChat.</string>
<string>Share your location with your contacts in Pangea Chat.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Share your location with your contacts in FluffyChat.</string>
<string>Share your location with your contacts in Pangea Chat.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record voice message and share them with your contacts on FluffyChat.</string>
<string>Record voice message and share them with your contacts on Pangea Chat.</string>
<key>NSMotionUsageDescription</key>
<string>Share motions with your contacts in FluffyChat.</string>
<string>Share motions with your contacts in Pangea Chat.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Open photos from your gallery and share them with your contacts on FluffyChat.</string>
<string>Open photos from your gallery and share them with your contacts on Pangea Chat.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Share data with your contacts in FluffyChat.</string>
<string>Share data with your contacts in Pangea Chat.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>

View file

@ -9,8 +9,10 @@
<string>applinks:example.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<!-- #Pangea -->
<!-- <array>
<string>group.im.fluffychat.app</string>
</array>
</array> -->
<!-- Pangea# -->
</dict>
</plist>

View file

@ -3,3 +3,4 @@ template-arb-file: intl_en.arb
output-localization-file: l10n.dart
output-class: L10n
preferred-supported-locales: ["en"]
untranslated-messages-file: needed-translations.txt

View file

@ -1,13 +1,20 @@
import 'dart:ui';
import 'package:fluffychat/pangea/config/environment.dart';
import 'package:matrix/matrix.dart';
abstract class AppConfig {
static String _applicationName = 'FluffyChat';
// #Pangea
// static String _applicationName = 'FluffyChat';
static String _applicationName = 'Pangea Chat';
// #Pangea
static String get applicationName => _applicationName;
static String? _applicationWelcomeMessage;
static String? get applicationWelcomeMessage => _applicationWelcomeMessage;
static String _defaultHomeserver = 'matrix.org';
// #Pangea
// static String _defaultHomeserver = 'matrix.org';
static String _defaultHomeserver = Environment.synapsURL;
// #Pangea
static String get defaultHomeserver => _defaultHomeserver;
static double fontSizeFactor = 1;
static const Color chatColor = primaryColor;
@ -15,24 +22,37 @@ abstract class AppConfig {
static const double messageFontSize = 15.75;
static const bool allowOtherHomeservers = true;
static const bool enableRegistration = true;
static const Color primaryColor = Color(0xFF5625BA);
static const Color primaryColorLight = Color(0xFFCCBDEA);
// #Pangea
// static const Color primaryColor = Color(0xFF5625BA);
// static const Color primaryColorLight = Color(0xFFCCBDEA);
static const Color primaryColor = Color(0xFF8560E0);
static const Color primaryColorLight = Color(0xFFDBC9FF);
static const Color secondaryColor = Color(0xFF41a2bc);
static String _privacyUrl =
'https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md';
static const Color activeToggleColor = Color(0xFF33D057);
// static String _privacyUrl =
// 'https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md';
static String _privacyUrl = "https://www.pangeachat.com/privacy";
//Pangea#
static String get privacyUrl => _privacyUrl;
static const String enablePushTutorial =
'https://github.com/krille-chan/fluffychat/wiki/Push-Notifications-without-Google-Services';
static const String encryptionTutorial =
'https://github.com/krille-chan/fluffychat/wiki/How-to-use-end-to-end-encryption-in-FluffyChat';
static const String appId = 'im.fluffychat.FluffyChat';
static const String appOpenUrlScheme = 'im.fluffychat';
// #Pangea
// static const String appOpenUrlScheme = 'im.fluffychat';
static const String appOpenUrlScheme = 'matrix.pangea.chat';
static String _webBaseUrl = 'https://fluffychat.im/web';
// Pangea#
static String get webBaseUrl => _webBaseUrl;
static const String sourceCodeUrl =
'https://github.com/krille-chan/fluffychat';
static const String supportUrl =
'https://github.com/krille-chan/fluffychat/issues';
//#Pangea
static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat';
// static const String supportUrl =
// 'https://gitlab.com/famedly/fluffychat/issues';
static const String supportUrl = 'https://www.pangeachat.com/faqs';
static const String termsOfServiceUrl =
'https://www.pangeachat.com/terms-of-service';
//Pangea#
static final Uri newIssueUrl = Uri(
scheme: 'https',
host: 'github.com',
@ -41,7 +61,10 @@ abstract class AppConfig {
static const bool enableSentry = true;
static const String sentryDns =
'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143';
static bool renderHtml = true;
//#Pangea
static bool renderHtml = false;
// static bool renderHtml = true;
//Pangea#
static bool hideRedactedEvents = false;
static bool hideUnknownEvents = true;
static bool hideUnimportantStateEvents = true;
@ -49,27 +72,55 @@ abstract class AppConfig {
static bool separateChatTypes = false;
static bool autoplayImages = true;
static bool sendTypingNotifications = true;
static bool sendOnEnter = false;
//#Pangea
static bool sendOnEnter = true;
// static bool sendOnEnter = false;
//Pangea#
static bool experimentalVoip = false;
static const bool hideTypingUsernames = false;
static const bool hideAllStateEvents = false;
static const String inviteLinkPrefix = 'https://matrix.to/#/';
static const String deepLinkPrefix = 'im.fluffychat://chat/';
static const String schemePrefix = 'matrix:';
static const String pushNotificationsChannelId = 'fluffychat_push';
static const String pushNotificationsChannelName = 'FluffyChat push channel';
// #Pangea
// static const String pushNotificationsChannelId = 'fluffychat_push';
// static const String pushNotificationsChannelName = 'FluffyChat push channel';
// static const String pushNotificationsChannelDescription =
// 'Push notifications for FluffyChat';
// static const String pushNotificationsAppId = 'chat.fluffy.fluffychat';
// static const String pushNotificationsGatewayUrl =
// 'https://push.fluffychat.im/_matrix/push/v1/notify';
// static const String pushNotificationsPusherFormat = 'event_id_only';
static const String pushNotificationsChannelId = 'pangeachat_push';
static const String pushNotificationsChannelName = 'Pangea Chat push channel';
static const String pushNotificationsChannelDescription =
'Push notifications for FluffyChat';
static const String pushNotificationsAppId = 'chat.fluffy.fluffychat';
'Push notifications for Pangea Chat';
static const String pushNotificationsAppId = 'com.talktolearn.chat';
static const String pushNotificationsGatewayUrl =
'https://push.fluffychat.im/_matrix/push/v1/notify';
static const String pushNotificationsPusherFormat = 'event_id_only';
'https://sygnal.pangea.chat/_matrix/push/v1/notify';
static const String? pushNotificationsPusherFormat = null;
// Pangea#
static const String emojiFontName = 'Noto Emoji';
static const String emojiFontUrl =
'https://github.com/googlefonts/noto-emoji/';
static const double borderRadius = 16.0;
static const double columnWidth = 360.0;
// #Pangea
static String googlePlayMangementUrl =
"https://play.google.com/store/account/subscriptions";
static String googlePlayHistoryUrl =
"https://play.google.com/store/account/orderhistory";
static String googlePlayPaymentMethodUrl =
"https://play.google.com/store/paymentmethods";
static String appleMangementUrl =
"https://apps.apple.com/account/subscriptions";
static String stripePerMonth =
"https://buy.stripe.com/test_bIY6ssd8z5Uz8ec8ww";
static String iosPromoCode =
"https://apps.apple.com/redeem?ctx=offercodes&id=1445118630&code=";
// Pangea#
static void loadFromJson(Map<String, dynamic> json) {
if (json['chat_color'] != null) {
try {
@ -97,7 +148,11 @@ abstract class AppConfig {
_privacyUrl = json['web_base_url'];
}
if (json['render_html'] is bool) {
renderHtml = json['render_html'];
// #Pangea
// this is interfering with our PangeaRichText functionality, removing it for now
renderHtml = false;
// renderHtml = json['render_html'];
// Pangea#
}
if (json['hide_redacted_events'] is bool) {
hideRedactedEvents = json['hide_redacted_events'];

View file

@ -0,0 +1,91 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyAXK_jobz9YjNmiS1leA-vbGd_a8W-TCGI',
appId: '1:545984292675:web:80f3babc12328eddb931f6',
messagingSenderId: '545984292675',
projectId: 'pangea-chat-936ee',
authDomain: 'pangea-chat-936ee.firebaseapp.com',
databaseURL: 'https://pangea-chat-936ee-default-rtdb.firebaseio.com',
storageBucket: 'pangea-chat-936ee.appspot.com',
measurementId: 'G-FKP13VDEBX',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyAyWBbl83WXzbVr6txyCmlUsZhpWomQfdg',
appId: '1:545984292675:android:d808acce7a80c20bb931f6',
messagingSenderId: '545984292675',
projectId: 'pangea-chat-936ee',
databaseURL: 'https://pangea-chat-936ee-default-rtdb.firebaseio.com',
storageBucket: 'pangea-chat-936ee.appspot.com',
androidClientId:
'545984292675-2amsnoan1mt6lec1fld1a7eagu6gej7o.apps.googleusercontent.com'
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCl8QZd9_PnaqJY2zLHCwlsmSWdq7hnH-U',
appId: '1:545984292675:ios:1226406ecc36e056b931f6',
messagingSenderId: '545984292675',
projectId: 'pangea-chat-936ee',
databaseURL: 'https://pangea-chat-936ee-default-rtdb.firebaseio.com',
storageBucket: 'pangea-chat-936ee.appspot.com',
iosClientId:
'545984292675-f5p76l3h9sibsonrct7a8l9ca3c69at0.apps.googleusercontent.com',
iosBundleId: 'com.talktolearn.chat',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyCl8QZd9_PnaqJY2zLHCwlsmSWdq7hnH-U',
appId: '1:545984292675:ios:1226406ecc36e056b931f6',
messagingSenderId: '545984292675',
projectId: 'pangea-chat-936ee',
databaseURL: 'https://pangea-chat-936ee-default-rtdb.firebaseio.com',
storageBucket: 'pangea-chat-936ee.appspot.com',
iosClientId:
'545984292675-f5p76l3h9sibsonrct7a8l9ca3c69at0.apps.googleusercontent.com',
iosBundleId: 'com.talktolearn.chat',
);
}

View file

@ -1,15 +1,9 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/add_story/add_story.dart';
import 'package:fluffychat/pages/archive/archive.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_members/chat_members.dart';
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings.dart';
@ -30,24 +24,46 @@ import 'package:fluffychat/pages/settings_notifications/settings_notifications.d
import 'package:fluffychat/pages/settings_security/settings_security.dart';
import 'package:fluffychat/pages/settings_stories/settings_stories.dart';
import 'package:fluffychat/pages/settings_style/settings_style.dart';
import 'package:fluffychat/pages/story/story_page.dart';
import 'package:fluffychat/pangea/guard/p_vguard.dart';
import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart';
import 'package:fluffychat/pangea/pages/class_settings/class_settings_page.dart';
import 'package:fluffychat/pangea/pages/exchange/add_exchange_to_class.dart';
import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart';
import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription.dart';
import 'package:fluffychat/pangea/pages/sign_up/signup.dart';
import 'package:fluffychat/pangea/widgets/class/join_with_link.dart';
import 'package:fluffychat/widgets/layouts/empty_page.dart';
import 'package:fluffychat/widgets/layouts/two_column_layout.dart';
import 'package:fluffychat/widgets/log_view.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
import '../pangea/pages/analytics/class_analytics/class_analytics.dart';
import '../pangea/pages/analytics/class_list/class_list.dart';
abstract class AppRoutes {
static FutureOr<String?> loggedInRedirect(
BuildContext context,
GoRouterState state,
) =>
Matrix.of(context).client.isLogged() ? '/rooms' : null;
) {
// #Pangea
// Matrix.of(context).client.isLogged() ? '/rooms' : null;
return PAuthGaurd.loggedInRedirect(context, state);
// Pangea#
}
static FutureOr<String?> loggedOutRedirect(
BuildContext context,
GoRouterState state,
) =>
Matrix.of(context).client.isLogged() ? null : '/home';
) {
// #Pangea
// Matrix.of(context).client.isLogged() ? null : '/home';
return PAuthGaurd.loggedOutRedirect(context, state);
// Pangea#
}
AppRoutes();
@ -73,6 +89,16 @@ abstract class AppRoutes {
),
redirect: loggedInRedirect,
),
// #Pangea
GoRoute(
path: 'signup',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const SignupPage(),
),
redirect: loggedInRedirect,
),
// Pangea#
],
),
GoRoute(
@ -100,6 +126,26 @@ abstract class AppRoutes {
: child,
),
routes: [
// #Pangea
GoRoute(
path: '/spaces/:roomid',
pageBuilder: (context, state) => defaultPageBuilder(
context,
ChatDetails(
roomId: state.pathParameters['roomid']!,
),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: '/join_with_link',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const JoinClassWithLink(),
),
redirect: loggedOutRedirect,
),
// Pangea#
GoRoute(
path: '/rooms',
redirect: loggedOutRedirect,
@ -112,32 +158,68 @@ abstract class AppRoutes {
),
),
routes: [
// #Pangea
GoRoute(
path: 'stories/create',
path: 'user_age',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const AddStoryPage(),
const PUserAge(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'stories/:roomid',
path: 'mylearning',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const StoryPage(),
const StudentAnalyticsPage(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'analytics',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const AnalyticsClassList(),
),
redirect: loggedOutRedirect,
routes: [
GoRoute(
path: 'share',
path: ':classid',
redirect: loggedOutRedirect,
pageBuilder: (context, state) => defaultPageBuilder(
context,
const AddStoryPage(),
const ClassAnalyticsPage(),
),
redirect: loggedOutRedirect,
),
],
),
// GoRoute(
// path: 'stories/create',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const AddStoryPage(),
// ),
// redirect: loggedOutRedirect,
// ),
// GoRoute(
// path: 'stories/:roomid',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const StoryPage(),
// ),
// redirect: loggedOutRedirect,
// routes: [
// GoRoute(
// path: 'share',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const AddStoryPage(),
// ),
// redirect: loggedOutRedirect,
// ),
// ],
// ),
// Pangea#
GoRoute(
path: 'archive',
pageBuilder: (context, state) => defaultPageBuilder(
@ -167,7 +249,10 @@ abstract class AppRoutes {
redirect: loggedOutRedirect,
),
GoRoute(
path: 'newgroup',
// #Pangea
// path: 'newgroup',
path: 'newgroup/:spaceid',
// Pangea#
pageBuilder: (context, state) => defaultPageBuilder(
context,
const NewGroup(),
@ -182,6 +267,32 @@ abstract class AppRoutes {
),
redirect: loggedOutRedirect,
),
// #Pangea
GoRoute(
path: 'newspace/:newexchange',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const NewSpace(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'join_exchange/:exchangeid',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const AddExchangeToClass(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'partner',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const FindPartner(),
),
redirect: loggedOutRedirect,
),
// Pangea#
ShellRoute(
pageBuilder: (context, state, child) => defaultPageBuilder(
context,
@ -244,24 +355,26 @@ abstract class AppRoutes {
],
redirect: loggedOutRedirect,
),
GoRoute(
path: 'addaccount',
redirect: loggedOutRedirect,
pageBuilder: (context, state) => defaultPageBuilder(
context,
const HomeserverPicker(),
),
routes: [
GoRoute(
path: 'login',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const Login(),
),
redirect: loggedOutRedirect,
),
],
),
// #Pangea
// GoRoute(
// path: 'addaccount',
// redirect: loggedOutRedirect,
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const HomeserverPicker(),
// ),
// routes: [
// GoRoute(
// path: 'login',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const Login(),
// ),
// redirect: loggedOutRedirect,
// ),
// ],
// ),
// Pangea#
GoRoute(
path: 'security',
redirect: loggedOutRedirect,
@ -296,6 +409,24 @@ abstract class AppRoutes {
),
],
),
// #Pangea
GoRoute(
path: 'learning',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const SettingsLearning(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'subscription',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const SubscriptionManagement(),
),
redirect: loggedOutRedirect,
),
// Pangea#
],
redirect: loggedOutRedirect,
),
@ -309,14 +440,16 @@ abstract class AppRoutes {
),
redirect: loggedOutRedirect,
routes: [
GoRoute(
path: 'encryption',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const ChatEncryptionSettings(),
),
redirect: loggedOutRedirect,
),
// #Pangea
// GoRoute(
// path: 'encryption',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// const ChatEncryptionSettings(),
// ),
// redirect: loggedOutRedirect,
// ),
// Pangea#
GoRoute(
path: 'invite',
pageBuilder: (context, state) => defaultPageBuilder(
@ -354,6 +487,16 @@ abstract class AppRoutes {
),
redirect: loggedOutRedirect,
),
// #Pangea
GoRoute(
path: 'class_settings',
pageBuilder: (context, state) => defaultPageBuilder(
context,
const ClassSettingsPage(),
),
redirect: loggedOutRedirect,
),
// Pangea#
GoRoute(
path: 'invite',
pageBuilder: (context, state) => defaultPageBuilder(
@ -391,6 +534,17 @@ abstract class AppRoutes {
],
redirect: loggedOutRedirect,
),
// GoRoute(
// path: 'tasks',
// pageBuilder: (context, state) => defaultPageBuilder(
// context,
// TasksPage(
// room: Matrix.of(context)
// .client
// .getRoomById(state.pathParameters['roomid']!)!,
// ),
// ),
// ),
],
),
],

View file

@ -1,7 +1,8 @@
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'app_config.dart';
abstract class FluffyThemes {
@ -140,6 +141,11 @@ abstract class FluffyThemes {
),
),
),
// #Pangea
cupertinoOverrideTheme: const CupertinoThemeData(
textTheme: CupertinoTextThemeData(),
),
// Pangea#
);
}
}

View file

@ -1,12 +1,17 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/config/environment.dart';
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_storage/get_storage.dart';
import 'package:matrix/matrix.dart';
import 'config/setting_keys.dart';
import 'utils/background_push.dart';
import 'widgets/fluffy_chat_app.dart';
@ -14,6 +19,23 @@ import 'widgets/fluffy_chat_app.dart';
void main() async {
Logs().i('Welcome to ${AppConfig.applicationName} <3');
// #Pangea
await dotenv.load(fileName: Environment.fileName);
await Future.wait([
ErrorHandler.initialize(),
PangeaLanguage.initialize(),
GoogleAnalytics.initialize(),
]);
///
/// PangeaLanguage must be initialized before the runApp
/// Then where ever you need language functions simply call PangeaLanguage pangeaLanguage = PangeaLanguage()
/// pangeaLanguage.getList or whatever function you need
///
await GetStorage.init();
// Pangea#
// Our background push shared isolate accesses flutter-internal things very early in the startup proccess
// To make sure that the parts of flutter needed are started up already, we need to ensure that the
// widget bindings are initialized already.

View file

@ -1,16 +1,8 @@
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:file_picker/file_picker.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart';
import 'package:video_player/video_player.dart';
import 'package:fluffychat/pages/add_story/add_story_view.dart';
import 'package:fluffychat/pages/add_story/invite_story_page.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
@ -19,6 +11,13 @@ import 'package:fluffychat/utils/story_theme_data.dart';
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart';
import 'package:video_player/video_player.dart';
import '../../utils/matrix_sdk_extensions/client_stories_extension.dart';
class AddStoryPage extends StatefulWidget {

View file

@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/archive/archive.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
class ArchiveView extends StatelessWidget {
final ArchiveController controller;

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
@ -8,9 +10,6 @@ import 'package:matrix/encryption.dart';
import 'package:matrix/encryption/utils/bootstrap.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import '../../utils/adaptive_bottom_sheet.dart';
import '../key_verification/key_verification_dialog.dart';

View file

@ -1,10 +1,8 @@
import 'package:fluffychat/pages/chat/add_widget_tile.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pages/chat/add_widget_tile.dart';
class AddWidgetTileView extends StatelessWidget {
final AddWidgetTileState controller;

View file

@ -1,15 +1,42 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat_view.dart';
import 'package:fluffychat/pages/chat/event_info_dialog.dart';
import 'package:fluffychat/pages/chat/recording_dialog.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/message_data_models.dart';
import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/pangea/utils/instructions.dart';
import 'package:fluffychat/pangea/utils/report_message.dart';
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
@ -19,19 +46,6 @@ import 'package:record/record.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat_view.dart';
import 'package:fluffychat/pages/chat/event_info_dialog.dart';
import 'package:fluffychat/pages/chat/recording_dialog.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/account_bundles.dart';
import '../../utils/localized_exception_extension.dart';
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
@ -104,6 +118,10 @@ class ChatPageWithRoom extends StatefulWidget {
}
class ChatController extends State<ChatPageWithRoom> {
// #Pangea
final PangeaController pangeaController = MatrixState.pangeaController;
late Choreographer choreographer = Choreographer(pangeaController, this);
// Pangea#
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
late Client sendingClient;
@ -148,6 +166,9 @@ class ChatController extends State<ChatPageWithRoom> {
).detectFileType,
);
}
// #Pangea
if (matrixFiles.isEmpty) return;
// Pangea#
await showDialog(
context: context,
@ -210,7 +231,10 @@ class ChatController extends State<ChatPageWithRoom> {
.firstWhere((s) => s.rooms?.leave?.containsKey(room.id) ?? false);
await room.leave();
await waitForSync;
return await client.startDirectChat(userId);
//#Pangea
// return await client.startDirectChat(userId);
return await client.startDirectChat(userId, enableEncryption: false);
//Pangea#
},
);
final roomId = success.result;
@ -229,7 +253,10 @@ class ChatController extends State<ChatPageWithRoom> {
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
void requestHistory() async {
// #Pangea
// void requestHistory() async {
Future<void> requestHistory() async {
// #Pangea
if (!timeline!.canRequestHistory) return;
Logs().v('Requesting history...');
try {
@ -290,6 +317,10 @@ class ChatController extends State<ChatPageWithRoom> {
}
}
// #Pangea
bool showPermissionsError = false;
// #Pangea
@override
void initState() {
scrollController.addListener(_updateScrollController);
@ -297,6 +328,47 @@ class ChatController extends State<ChatPageWithRoom> {
_loadDraft();
super.initState();
sendingClient = Matrix.of(context).client;
// #Pangea
if (!mounted) return;
Future.delayed(const Duration(seconds: 1), () async {
if (!mounted) return;
debugPrint(
"chat.dart l1 ${pangeaController.languageController.activeL1Code(roomID: roomId)}",
);
debugPrint(
"chat.dart l2 ${pangeaController.languageController.activeL2Code(roomID: roomId)}",
);
if (mounted) {
pangeaController.languageController.showDialogOnEmptyLanguage(
context,
() => Future.delayed(
Duration.zero,
() => setState(
() {},
),
),
);
}
await Matrix.of(context).client.roomsLoading;
choreographer.setRoomId(roomId);
choreographer.messageOptions.resetSelectedDisplayLang();
choreographer.stateListener.stream.listen((event) {
debugPrint("chat.dart choreo event $event");
setState(() {});
});
showPermissionsError = !pangeaController.permissionsController
.isToolEnabled(ToolSetting.interactiveTranslator, room) ||
!pangeaController.permissionsController
.isToolEnabled(ToolSetting.interactiveGrammar, room);
});
Future.delayed(
const Duration(seconds: 5),
() {
if (mounted) setState(() => showPermissionsError = false);
},
);
// Pangea#
_tryLoadTimeline();
}
@ -351,6 +423,27 @@ class ChatController extends State<ChatPageWithRoom> {
onUpdate: updateView,
eventContextId: eventContextId,
);
// #Pangea
List<Event>? messageEvents =
timeline?.events.where((x) => x.type == 'm.room.message').toList();
if (messageEvents != null && messageEvents.length < 10) {
int prevNumEvents = timeline!.events.length;
await requestHistory();
messageEvents =
timeline?.events.where((x) => x.type == 'm.room.message').toList();
int numRequests = 0;
while (timeline!.events.length > prevNumEvents &&
messageEvents!.length < 10 &&
numRequests <= 5) {
prevNumEvents = timeline!.events.length;
await requestHistory();
messageEvents = timeline?.events
.where((x) => x.type == 'm.room.message')
.toList();
numRequests++;
}
}
// #Pangea
} catch (e, s) {
Logs().w('Unable to load timeline on event ID $eventContextId', e, s);
if (!mounted) return;
@ -408,10 +501,17 @@ class ChatController extends State<ChatPageWithRoom> {
timeline?.cancelSubscriptions();
timeline = null;
inputFocus.removeListener(_inputFocusListener);
//#Pangea
choreographer.stateListener.close();
choreographer.dispose();
//Pangea#
super.dispose();
}
TextEditingController sendController = TextEditingController();
// #Pangea
// TextEditingController sendController = TextEditingController();
PangeaTextController get sendController => choreographer.textController;
// #Pangea
void setSendingClient(Client c) {
// first cancel typing with the old sending client
@ -439,7 +539,20 @@ class ChatController extends State<ChatPageWithRoom> {
Matrix.of(context).setActiveClient(c);
});
Future<void> send() async {
// #Pangea
// Future<void> send() async {
// Original send function gets the tx id within the matrix lib,
// but for choero, the tx id is generated before the message send.
// Also, adding PangeaMessageData
Future<void> send({
PangeaRepresentation? originalSent,
PangeaRepresentation? originalWritten,
PangeaMessageTokens? tokensSent,
PangeaMessageTokens? tokensWritten,
ChoreoRecord? choreo,
UseType? useType,
}) async {
// Pangea#
if (sendController.text.trim().isEmpty) return;
_storeInputTimeoutTimer?.cancel();
final prefs = await SharedPreferences.getInstance();
@ -463,12 +576,70 @@ class ChatController extends State<ChatPageWithRoom> {
}
// ignore: unawaited_futures
room.sendTextEvent(
// #Pangea
// room.sendTextEvent(
// sendController.text,
// inReplyTo: replyEvent,
// editEventId: editEvent?.eventId,
// parseCommands: parseCommands,
// );
room
.pangeaSendTextEvent(
sendController.text,
inReplyTo: replyEvent,
editEventId: editEvent?.eventId,
parseCommands: parseCommands,
originalSent: originalSent,
originalWritten: originalWritten,
tokensSent: tokensSent,
tokensWritten: tokensWritten,
choreo: choreo,
useType: useType,
)
//#Pangea
.then(
(String? msgEventId) {
GoogleAnalytics.sendMessage(
room.id,
room.classCode,
useType ?? UseType.un,
);
if (msgEventId == null) {
ErrorHandler.logError(
e: Exception('msgEventId is null'),
s: StackTrace.current,
);
return;
}
pangeaController.myAnalytics.handleMessage(
room,
RecentMessageRecord(
eventId: msgEventId,
chatId: room.id,
useType: useType ?? UseType.un,
time: DateTime.now(),
),
);
if (choreo != null &&
tokensSent != null &&
originalSent?.langCode ==
pangeaController.languageController
.activeL2Code(roomID: room.id)) {
pangeaController.myAnalytics.saveConstructsMixed(
[
// ...choreo.toVocabUse(tokensSent.tokens, room.id, msgEventId),
...choreo.toGrammarConstructUse(msgEventId, room.id),
],
originalSent!.langCode,
);
}
},
onError: (err, stack) => ErrorHandler.logError(e: err, s: stack),
);
// Pangea#
sendController.value = TextEditingValue(
text: pendingText,
selection: const TextSelection.collapsed(offset: 0),
@ -669,6 +840,11 @@ class ChatController extends State<ChatPageWithRoom> {
}
void emojiPickerAction() {
// #Pangea
if (choreographer.itController.isOpen) {
return;
}
// Pangea#
if (showEmojiPicker) {
inputFocus.requestFocus();
} else {
@ -751,16 +927,36 @@ class ChatController extends State<ChatPageWithRoom> {
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
);
if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog(
context: context,
future: () => Matrix.of(context).client.reportContent(
event.roomId!,
event.eventId,
reason: reason.single,
score: score,
// #Pangea
try {
await reportMessage(
context,
roomId,
reason.single,
event.senderId,
event.content['body'].toString(),
);
} catch (err) {
ErrorHandler.logError(e: err, s: StackTrace.current);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)!.oopsSomethingWentWrong,
),
);
if (result.error != null) return;
),
);
}
// final result = await showFutureLoadingDialog(
// context: context,
// future: () => Matrix.of(context).client.reportContent(
// event.roomId!,
// event.eventId,
// reason: reason.single,
// score: score,
// ),
// );
// if (result.error != null) return;
// Pangea#
setState(() {
showEmojiPicker = false;
selectedEvents.clear();
@ -1019,6 +1215,9 @@ class ChatController extends State<ChatPageWithRoom> {
void clearSelectedEvents() => setState(() {
selectedEvents.clear();
showEmojiPicker = false;
//#Pangea
choreographer.messageOptions.resetSelectedDisplayLang();
//Pangea#
});
void clearSingleSelectedEvent() {
@ -1084,6 +1283,16 @@ class ChatController extends State<ChatPageWithRoom> {
}
void onSelectMessage(Event event) {
// #Pangea
if (choreographer.itController.isOpen) {
return;
}
pangeaController.instructions.show(
context,
InstructionsEnum.understandingMessages,
event.eventId,
);
// Pangea#
if (!event.redacted) {
if (selectedEvents.contains(event)) {
setState(
@ -1118,12 +1327,22 @@ class ChatController extends State<ChatPageWithRoom> {
return index + 1;
}
void onInputBarSubmitted(_) {
send();
// #Pangea
void onInputBarSubmitted(String _, BuildContext context) {
// void onInputBarSubmitted(_) {
// send();
choreographer.send(context);
// Pangea#
FocusScope.of(context).requestFocus(inputFocus);
}
void onAddPopupMenuButtonSelected(String choice) {
//#Pangea
void onAddPopupMenuButtonSelected(String? choice) {
// void onAddPopupMenuButtonSelected(String choice) {
if (choice == null) {
debugger(when: kDebugMode);
}
//Pangea#
if (choice == 'file') {
sendFileAction();
}
@ -1299,6 +1518,37 @@ class ChatController extends State<ChatPageWithRoom> {
editEvent = null;
});
// #Pangea
double? availableSpace;
double? inputRowSize;
bool? lastState;
bool get isRowScrollable {
if (availableSpace == null || inputRowSize == null) {
if (lastState == null) {
lastState = false;
Future.delayed(Duration.zero, () {
setState(() {});
});
}
return false;
}
const double offSetValue = 10;
final bool currentState = inputRowSize! > (availableSpace! - offSetValue);
if (!lastState! && currentState) {
Future.delayed(Duration.zero, () {
setState(() {});
});
}
if (lastState! && !currentState) {
Future.delayed(Duration.zero, () {
setState(() {});
});
}
lastState = currentState;
return currentState;
}
// #Pangea
@override
Widget build(BuildContext context) => ChatView(this);
}

View file

@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
class ChatAppBarTitle extends StatelessWidget {
final ChatController controller;

View file

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'chat.dart';
class ChatEmojiPicker extends StatelessWidget {

View file

@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/pages/chat/seen_by_row.dart';
import 'package:fluffychat/pages/chat/typing_indicators.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/widgets/chat/locked_chat_message.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ChatEventList extends StatelessWidget {
final ChatController controller;
@ -76,6 +76,14 @@ class ChatEventList extends StatelessWidget {
);
}
// #Pangea
if (i == 1) {
return controller.room.locked && !controller.room.isRoomAdmin
? const LockedChatMessage()
: const SizedBox.shrink();
}
// Pangea#
// Request history button or progress indicator:
if (i == controller.timeline!.events.length + 1) {
if (controller.timeline!.isRequestingHistory) {
@ -102,11 +110,17 @@ class ChatEventList extends StatelessWidget {
}
// The message at this index:
final event = controller.timeline!.events[i - 1];
// #Pangea
// final event = controller.timeline!.events[i - 1];
final event = controller.timeline!.events[i - 2];
// Pangea#
return AutoScrollTag(
key: ValueKey(event.eventId),
index: i - 1,
// #Pangea
// index: i - 1,
index: i - 2,
// Pangea#
controller: controller.scrollController,
child: event.isVisibleInGui
? Message(
@ -126,7 +140,13 @@ class ChatEventList extends StatelessWidget {
onSelect: controller.onSelectMessage,
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isNotEmpty,
// #Pangea
// longPressSelect: controller.selectedEvents.isEmpty,
selectedDisplayLang: controller
.choreographer.messageOptions.selectedDisplayLang,
immersionMode: controller.choreographer.immersionMode,
definitions: controller.choreographer.definitionsEnabled,
// Pangea#
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
timeline: controller.timeline!,

View file

@ -1,15 +1,16 @@
import 'package:animations/animations.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../config/themes.dart';
import 'chat.dart';
import 'input_bar.dart';
@ -25,252 +26,302 @@ class ChatInputRow extends StatelessWidget {
controller.emojiPickerType == EmojiPickerType.reaction) {
return const SizedBox.shrink();
}
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: controller.selectMode
? <Widget>[
SizedBox(
height: 56,
child: TextButton(
onPressed: controller.forwardEventsAction,
child: Row(
children: <Widget>[
const Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context)!.forward),
],
),
),
),
controller.selectedEvents.length == 1
? controller.selectedEvents.first
.getDisplayEvent(controller.timeline!)
.status
.isSent
? SizedBox(
height: 56,
child: TextButton(
onPressed: controller.replyAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)!.reply),
const Icon(Icons.keyboard_arrow_right),
],
),
),
)
: SizedBox(
height: 56,
child: TextButton(
onPressed: controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)!.tryToSendAgain),
const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16),
],
),
),
)
: const SizedBox.shrink(),
]
: <Widget>[
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyA,
},
onKeysPressed: () =>
controller.onAddPopupMenuButtonSelected('file'),
helpLabel: L10n.of(context)!.sendFile,
child: AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
height: 56,
width: controller.inputText.isEmpty ? 56 : 0,
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: PopupMenuButton<String>(
icon: const Icon(Icons.add_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child: Icon(Icons.attachment_outlined),
),
title: Text(L10n.of(context)!.sendFile),
contentPadding: const EdgeInsets.all(0),
),
// #Pangea
return Column(
children: [
ITBar(
choreographer: controller.choreographer,
),
Row(
// crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
// Pangea#
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: controller.selectMode
? <Widget>[
SizedBox(
height: 56,
child: TextButton(
onPressed: controller.forwardEventsAction,
child: Row(
children: <Widget>[
const Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context)!.forward),
],
),
PopupMenuItem<String>(
value: 'image',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.image_outlined),
),
title: Text(L10n.of(context)!.sendImage),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context)!.openCamera),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context)!.openVideoCamera),
contentPadding: const EdgeInsets.all(0),
),
),
if (controller.room
.getImagePacks(ImagePackUsage.sticker)
.isNotEmpty)
PopupMenuItem<String>(
value: 'sticker',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
child: Icon(Icons.emoji_emotions_outlined),
),
title: Text(L10n.of(context)!.sendSticker),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'location',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.brown,
foregroundColor: Colors.white,
child: Icon(Icons.gps_fixed_outlined),
),
title: Text(L10n.of(context)!.shareLocation),
contentPadding: const EdgeInsets.all(0),
),
),
],
),
),
),
),
Container(
height: 56,
alignment: Alignment.center,
child: KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyE,
},
onKeysPressed: controller.emojiPickerAction,
helpLabel: L10n.of(context)!.emojis,
child: IconButton(
tooltip: L10n.of(context)!.emojis,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
controller.selectedEvents.length == 1
? controller.selectedEvents.first
.getDisplayEvent(controller.timeline!)
.status
.isSent
? SizedBox(
height: 56,
child: TextButton(
onPressed: controller.replyAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)!.reply),
const Icon(Icons.keyboard_arrow_right),
],
),
),
)
: SizedBox(
height: 56,
child: TextButton(
onPressed: controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)!.tryToSendAgain),
const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16),
],
),
),
)
: const SizedBox.shrink(),
]
: <Widget>[
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyA,
},
onKeysPressed: () =>
controller.onAddPopupMenuButtonSelected('file'),
helpLabel: L10n.of(context)!.sendFile,
child: AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
height: 56,
//#Pangea
// width: controller.inputText.isEmpty ? 56 : 0,
width: controller.inputText.isEmpty &&
controller.pangeaController.permissionsController
.showChatInputAddButton(controller.roomId)
? 56
: 0,
//Pangea#
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: PopupMenuButton<String>(
icon: const Icon(Icons.add_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
//#Pangea
if (controller.pangeaController.permissionsController
.canShareFile(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child: Icon(Icons.attachment_outlined),
),
title: Text(L10n.of(context)!.sendFile),
contentPadding: const EdgeInsets.all(0),
),
),
//#Pangea
if (controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'image',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.image_outlined),
),
title: Text(L10n.of(context)!.sendImage),
contentPadding: const EdgeInsets.all(0),
),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context)!.openCamera),
contentPadding: const EdgeInsets.all(0),
),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareVideo(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context)!.openVideoCamera),
contentPadding: const EdgeInsets.all(0),
),
),
if (controller.room
.getImagePacks(ImagePackUsage.sticker)
.isNotEmpty)
PopupMenuItem<String>(
value: 'sticker',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
child: Icon(Icons.emoji_emotions_outlined),
),
title: Text(L10n.of(context)!.sendSticker),
contentPadding: const EdgeInsets.all(0),
),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareLocation(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'location',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.brown,
foregroundColor: Colors.white,
child: Icon(Icons.gps_fixed_outlined),
),
title: Text(L10n.of(context)!.shareLocation),
contentPadding: const EdgeInsets.all(0),
),
),
],
),
),
),
Container(
height: 56,
alignment: Alignment.center,
child: KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyE,
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.emoji_emotions_outlined,
key: ValueKey(controller.showEmojiPicker),
onKeysPressed: controller.emojiPickerAction,
helpLabel: L10n.of(context)!.emojis,
child: IconButton(
tooltip: L10n.of(context)!.emojis,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.emoji_emotions_outlined,
key: ValueKey(controller.showEmojiPicker),
),
),
onPressed: controller.emojiPickerAction,
),
),
onPressed: controller.emojiPickerAction,
),
),
),
if (Matrix.of(context).isMultiAccount &&
Matrix.of(context).hasComplexBundles &&
Matrix.of(context).currentBundle!.length > 1)
Container(
height: 56,
alignment: Alignment.center,
child: _ChatAccountPicker(controller),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: InputBar(
room: controller.room,
minLines: 1,
maxLines: 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction:
AppConfig.sendOnEnter ? TextInputAction.send : null,
onSubmitted: controller.onInputBarSubmitted,
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,
decoration: InputDecoration(
hintText: L10n.of(context)!.writeAMessage,
hintMaxLines: 1,
border: InputBorder.none,
enabledBorder: InputBorder.none,
filled: false,
// #Pangea
// if (Matrix.of(context).isMultiAccount &&
// Matrix.of(context).hasComplexBundles &&
// Matrix.of(context).currentBundle!.length > 1)
// Container(
// height: 56,
// alignment: Alignment.center,
// child: _ChatAccountPicker(controller),
// ),
// Pangea#
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: InputBar(
room: controller.room,
minLines: 1,
maxLines: 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction:
AppConfig.sendOnEnter ? TextInputAction.send : null,
// #Pangea
// onSubmitted: controller.onInputBarSubmitted,
onSubmitted: (String value) =>
controller.onInputBarSubmitted(value, context),
// Pangea#
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,
decoration: InputDecoration(
hintText: L10n.of(context)!.writeAMessage,
hintMaxLines: 1,
border: InputBorder.none,
enabledBorder: InputBorder.none,
filled: false,
),
onChanged: controller.onInputBarChanged,
),
),
onChanged: controller.onInputBarChanged,
),
),
),
if (PlatformInfos.platformCanRecord &&
controller.inputText.isEmpty)
Container(
height: 56,
alignment: Alignment.center,
child: IconButton(
tooltip: L10n.of(context)!.voiceMessage,
icon: const Icon(Icons.mic_none_outlined),
onPressed: controller.voiceMessageAction,
),
),
if (!PlatformInfos.isMobile || controller.inputText.isNotEmpty)
Container(
height: 56,
alignment: Alignment.center,
child: IconButton(
icon: const Icon(Icons.send_outlined),
onPressed: controller.send,
tooltip: L10n.of(context)!.send,
),
),
],
if (PlatformInfos.platformCanRecord &&
controller.inputText.isEmpty)
Container(
height: 56,
alignment: Alignment.center,
child: IconButton(
tooltip: L10n.of(context)!.voiceMessage,
icon: const Icon(Icons.mic_none_outlined),
onPressed: controller.voiceMessageAction,
),
),
if (!PlatformInfos.isMobile ||
controller.inputText.isNotEmpty)
// #Pangea
ChoreographerSendButton(controller: controller),
// Container(
// height: 56,
// alignment: Alignment.center,
// child: IconButton(
// icon: const Icon(Icons.send_outlined),
// onPressed: controller.send,
// tooltip: L10n.of(context)!.send,
// ),
// ),
// Pangea#
],
),
],
);
}
}

View file

@ -1,25 +1,28 @@
import 'package:flutter/material.dart';
import 'package:badges/badges.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
import 'package:fluffychat/pages/chat/chat_event_list.dart';
import 'package:fluffychat/pages/chat/encryption_button.dart';
import 'package:fluffychat/pages/chat/pinned_events.dart';
import 'package:fluffychat/pages/chat/reactions_picker.dart';
import 'package:fluffychat/pages/chat/reply_display.dart';
import 'package:fluffychat/pages/chat/tombstone_display.dart';
import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart';
import 'package:fluffychat/pangea/choreographer/widgets/language_display_toggle.dart';
import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/pages/class_analytics/measure_able.dart';
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/unread_rooms_badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import '../../utils/stream_extension.dart';
import 'chat_emoji_picker.dart';
import 'chat_input_row.dart';
@ -34,6 +37,9 @@ class ChatView extends StatelessWidget {
List<Widget> _appBarActions(BuildContext context) {
if (controller.selectMode) {
return [
// #Pangea
LanguageDisplayToggle(controller: controller),
// Pangea#
if (controller.canEditSelectedEvents)
IconButton(
icon: const Icon(Icons.edit_outlined),
@ -108,33 +114,40 @@ class ChatView extends StatelessWidget {
],
),
];
} else if (controller.isArchived) {
return [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton.icon(
onPressed: controller.forgetRoom,
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
),
icon: const Icon(Icons.delete_forever_outlined),
label: Text(L10n.of(context)!.delete),
),
),
];
} else {
return [
if (Matrix.of(context).voipPlugin != null &&
controller.room.isDirectChat)
IconButton(
onPressed: controller.onPhoneButtonTap,
icon: const Icon(Icons.call_outlined),
tooltip: L10n.of(context)!.placeCall,
),
EncryptionButton(controller.room),
ChatSettingsPopupMenu(controller.room, true),
];
}
// #Pangea
// else if (controller.isArchived) {
// return [
// Padding(
// padding: const EdgeInsets.all(8.0),
// child: TextButton.icon(
// onPressed: controller.forgetRoom,
// style: TextButton.styleFrom(
// foregroundColor: Theme.of(context).colorScheme.error,
// ),
// icon: const Icon(Icons.delete_forever_outlined),
// label: Text(L10n.of(context)!.delete),
// ),
// ),
// ];
// }
//else {
// return [
// if (Matrix.of(context).voipPlugin != null &&
// controller.room.isDirectChat)
// IconButton(
// onPressed: controller.onPhoneButtonTap,
// icon: const Icon(Icons.call_outlined),
// tooltip: L10n.of(context)!.placeCall,
// ),
// EncryptionButton(controller.room),
// ChatSettingsPopupMenu(controller.room, true),
// ];
// }
return [
ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
];
// Pangea#
}
@override
@ -183,7 +196,12 @@ class ChatView extends StatelessWidget {
color: Theme.of(context).colorScheme.primary,
)
: UnreadRoomsBadge(
filter: (r) => r.id != controller.roomId,
filter: (r) =>
r.id != controller.roomId
// #Pangea
&&
!r.isAnalyticsRoom,
// Pangea#
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
child: const Center(child: BackButton()),
),
@ -191,17 +209,34 @@ class ChatView extends StatelessWidget {
title: ChatAppBarTitle(controller),
actions: _appBarActions(context),
),
floatingActionButton: controller.showScrollDownButton &&
controller.selectedEvents.isEmpty
? Padding(
padding: const EdgeInsets.only(bottom: 56.0),
child: FloatingActionButton(
onPressed: controller.scrollDown,
heroTag: null,
mini: true,
child: const Icon(Icons.arrow_downward_outlined),
),
)
// #Pangea
// floatingActionButton: controller.showScrollDownButton &&
// controller.selectedEvents.isEmpty
floatingActionButton: controller.selectedEvents.isEmpty
? (controller.showScrollDownButton
// Pangea#
? Padding(
padding: const EdgeInsets.only(bottom: 56.0),
child: FloatingActionButton(
onPressed: controller.scrollDown,
heroTag: null,
mini: true,
child: const Icon(Icons.arrow_downward_outlined),
),
)
// #Pangea
: controller.choreographer.errorService.error != null
? ChoreographerHasErrorButton(
controller.pangeaController,
controller.choreographer.errorService.error!,
)
: controller.showPermissionsError
? LanguagePermissionsButtons(
choreographer: controller.choreographer,
roomID: controller.roomId,
)
: null)
// Pangea#
: null,
body: DropTarget(
onDragDone: controller.onDragDone,
@ -284,95 +319,122 @@ class ChatView extends StatelessWidget {
),
if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join)
Container(
margin: EdgeInsets.only(
bottom: bottomSheetPadding,
left: bottomSheetPadding,
right: bottomSheetPadding,
),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 2.5,
),
alignment: Alignment.center,
child: Material(
borderRadius: const BorderRadius.only(
bottomLeft:
Radius.circular(AppConfig.borderRadius),
bottomRight:
Radius.circular(AppConfig.borderRadius),
),
elevation: 4,
shadowColor: Colors.black.withAlpha(64),
clipBehavior: Clip.hardEdge,
color: Theme.of(context).brightness ==
Brightness.light
? Colors.white
: Colors.black,
child: controller.room.isAbandonedDMRoom ==
true
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(16),
foregroundColor:
Theme.of(context)
.colorScheme
.error,
),
icon: const Icon(
Icons.archive_outlined,
),
onPressed: controller.leaveChat,
label: Text(
L10n.of(context)!.leave,
),
),
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(16),
),
icon: const Icon(
Icons.forum_outlined,
),
onPressed:
controller.recreateChat,
label: Text(
L10n.of(context)!.reopenChat,
),
),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
ReactionsPicker(controller),
ReplyDisplay(controller),
ChatInputRow(controller),
ChatEmojiPicker(controller),
],
// #Pangea
ConditionalFlexible(
isScroll: controller.isRowScrollable,
child: ConditionalScroll(
isScroll: controller.isRowScrollable,
child: MeasurableWidget(
onChange: (size, position) {
controller.inputRowSize = size!.height;
},
child:
// Pangea#
Container(
margin: EdgeInsets.only(
bottom: bottomSheetPadding,
left: bottomSheetPadding,
right: bottomSheetPadding,
),
constraints: const BoxConstraints(
maxWidth:
FluffyThemes.columnWidth * 2.5,
),
alignment: Alignment.center,
child: Material(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(
AppConfig.borderRadius,
),
bottomRight: Radius.circular(
AppConfig.borderRadius,
),
),
elevation: 4,
shadowColor: Colors.black.withAlpha(64),
clipBehavior: Clip.hardEdge,
color: Theme.of(context).brightness ==
Brightness.light
? Colors.white
: Colors.black,
child: controller
.room.isAbandonedDMRoom ==
true
? Row(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(
16,
),
foregroundColor:
Theme.of(context)
.colorScheme
.error,
),
icon: const Icon(
Icons.archive_outlined,
),
onPressed:
controller.leaveChat,
label: Text(
L10n.of(context)!.leave,
),
),
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(
16,
),
),
icon: const Icon(
Icons.forum_outlined,
),
onPressed:
controller.recreateChat,
label: Text(
L10n.of(context)!
.reopenChat,
),
),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
ReactionsPicker(controller),
ReplyDisplay(controller),
ChatInputRow(controller),
ChatEmojiPicker(controller),
],
),
),
),
),
),
),
],
),
// #Pangea
// if (controller.dragging)
// Container(
// color: Theme.of(context)
// .scaffoldBackgroundColor
// .withOpacity(0.9),
// alignment: Alignment.center,
// child: const Icon(
// Icons.upload_outlined,
// size: 100,
// ),
// ),
// Pangea#
),
if (controller.dragging)
Container(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.9),
alignment: Alignment.center,
child: const Icon(
Icons.upload_outlined,
size: 100,
),
),
],
),
),
@ -384,3 +446,35 @@ class ChatView extends StatelessWidget {
);
}
}
// #Pangea
Widget ConditionalFlexible({required bool isScroll, required Widget child}) {
if (isScroll) {
return Flexible(
flex: 9999999,
child: child,
);
}
return child;
}
class ConditionalScroll extends StatelessWidget {
final bool isScroll;
final Widget child;
const ConditionalScroll({
super.key,
required this.isScroll,
required this.child,
});
@override
Widget build(BuildContext context) {
if (isScroll) {
return SingleChildScrollView(
child: child,
);
}
return child;
}
}
// #Pangea

View file

@ -0,0 +1,50 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:url_launcher/link.dart';
import 'edit_widgets_dialog.dart';
class CupertinoWidgetsBottomSheet extends StatelessWidget {
final Room room;
const CupertinoWidgetsBottomSheet({Key? key, required this.room})
: super(key: key);
@override
Widget build(BuildContext context) {
return CupertinoActionSheet(
title: Text(L10n.of(context)!.matrixWidgets),
actions: [
...room.widgets.map(
(widget) => Link(
builder: (context, callback) {
return CupertinoActionSheetAction(
onPressed: callback ?? () {},
child: Text(widget.name ?? widget.url),
);
},
target: LinkTarget.blank,
uri: Uri.parse(widget.url),
),
),
CupertinoActionSheetAction(
child: Text(L10n.of(context)!.editWidgets),
onPressed: () {
Navigator.of(context).pop();
showCupertinoDialog(
context: context,
builder: (context) => EditWidgetsDialog(room: room),
useRootNavigator: false,
);
},
),
CupertinoActionSheetAction(
onPressed: Navigator.of(context).pop,
child: Text(L10n.of(context)!.cancel),
),
],
);
}
}

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';

View file

@ -1,15 +1,14 @@
import 'dart:async';
import 'dart:io';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import '../../../utils/matrix_sdk_extensions/event_extension.dart';
class AudioPlayerWidget extends StatefulWidget {

View file

@ -1,12 +1,10 @@
import 'dart:math';
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
class CuteContent extends StatefulWidget {
final Event event;

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_highlighter/flutter_highlighter.dart';
import 'package:flutter_highlighter/themes/shades-of-purple.dart';
import 'package:flutter_html/flutter_html.dart';
@ -9,9 +11,6 @@ import 'package:flutter_math_fork/flutter_math.dart';
import 'package:linkify/linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import '../../../utils/url_launcher.dart';
class HtmlMessage extends StatelessWidget {

View file

@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/image_viewer/image_viewer.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:matrix/matrix.dart';
class ImageBubble extends StatelessWidget {
final Event event;

View file

@ -1,14 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
import '../../../config/app_config.dart';
import 'message_content.dart';
import 'message_reactions.dart';
@ -28,6 +30,11 @@ class Message extends StatelessWidget {
final bool longPressSelect;
final bool selected;
final Timeline timeline;
// #Pangea
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
// Pangea#
const Message(
this.event, {
@ -41,6 +48,11 @@ class Message extends StatelessWidget {
required this.onSwipe,
this.selected = false,
required this.timeline,
// #Pangea
required this.selectedDisplayLang,
required this.immersionMode,
required this.definitions,
// Pangea#
Key? key,
}) : super(key: key);
@ -50,6 +62,9 @@ class Message extends StatelessWidget {
@override
Widget build(BuildContext context) {
// #Pangea
debugPrint('Message.build()');
// Pangea#
if (!{
EventTypes.Message,
EventTypes.Sticker,
@ -117,6 +132,15 @@ class Message extends StatelessWidget {
: Theme.of(context).colorScheme.primary;
}
// #Pangea
final pangeaMessageEvent = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: ownMessage,
selected: selected,
);
// Pangea#
final row = Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: rowMainAxisAlignment,
@ -191,100 +215,136 @@ class Message extends StatelessWidget {
color: noBubble ? Colors.transparent : color,
borderRadius: borderRadius,
clipBehavior: Clip.antiAlias,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
padding: noBubble || noPadding
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.relationshipType ==
RelationshipTypes.reply)
FutureBuilder<Event?>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData
? snapshot.data!
: Event(
eventId: event.relationshipEventId!,
content: {
'msgtype': 'm.text',
'body': '...',
},
senderId: event.senderId,
type: 'm.room.message',
room: event.room,
status: EventStatus.sent,
originServerTs: DateTime.now(),
);
return InkWell(
onTap: () {
if (scrollToEventId != null) {
scrollToEventId!(replyEvent.eventId);
}
},
child: AbsorbPointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 4.0,
),
child: ReplyContent(
replyEvent,
ownMessage: ownMessage,
timeline: timeline,
),
),
),
);
},
),
MessageContent(
displayEvent,
textColor: textColor,
onInfoTab: onInfoTab,
// #Pangea
child: CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey(event.eventId)
.link,
child: Container(
key: MatrixState.pAnyState
.layerLinkAndKey(event.eventId)
.key,
// Pangea#
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
padding: noBubble || noPadding
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
))
Padding(
padding: const EdgeInsets.only(
top: 4.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.edit_outlined,
color: textColor.withAlpha(164),
size: 14,
),
Text(
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.relationshipType ==
RelationshipTypes.reply)
FutureBuilder<Event?>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData
? snapshot.data!
: Event(
eventId: event.relationshipEventId!,
content: {
'msgtype': 'm.text',
'body': '...',
},
senderId: event.senderId,
type: 'm.room.message',
room: event.room,
status: EventStatus.sent,
originServerTs: DateTime.now(),
);
return InkWell(
onTap: () {
if (scrollToEventId != null) {
scrollToEventId!(replyEvent.eventId);
}
},
child: AbsorbPointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 4.0,
),
child: ReplyContent(
replyEvent,
ownMessage: ownMessage,
timeline: timeline,
),
),
),
),
],
);
},
),
MessageContent(
displayEvent,
textColor: textColor,
onInfoTab: onInfoTab,
// #Pangea
selected: selected,
pangeaMessageEvent: pangeaMessageEvent,
selectedDisplayLang: selectedDisplayLang,
immersionMode: immersionMode,
definitions: definitions,
// Pangea#
),
],
),
],
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
)
// #Pangea
||
(pangeaMessageEvent.showUseType)
// Pangea#
)
Padding(
padding: const EdgeInsets.only(
top: 4.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// #Pangea
if (pangeaMessageEvent.showUseType) ...[
pangeaMessageEvent.useType.iconView(
context,
textColor.withAlpha(164),
),
const SizedBox(width: 4)
],
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
)) ...[
// Pangea#
Icon(
Icons.edit_outlined,
color: textColor.withAlpha(164),
size: 14,
),
Text(
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,
),
),
],
],
),
),
],
),
],
),
),
),
),

View file

@ -1,19 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat/events/video_player.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/pangea/widgets/igc/pangea_rich_text.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import '../../../config/app_config.dart';
import '../../../utils/platform_infos.dart';
import '../../../utils/url_launcher.dart';
import '../../bootstrap/bootstrap_dialog.dart';
import 'audio_player.dart';
import 'cute_events.dart';
import 'html_message.dart';
@ -26,12 +26,29 @@ class MessageContent extends StatelessWidget {
final Event event;
final Color textColor;
final void Function(Event)? onInfoTab;
// #Pangea
final bool selected;
final PangeaMessageEvent pangeaMessageEvent;
//question: are there any performance benefits to using booleans
//here rather than passing the choreographer? pangea rich text, a widget
//further down in the chain is also using pangeaController so its not constant
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
// Pangea#
const MessageContent(
this.event, {
this.onInfoTab,
Key? key,
required this.textColor,
// #Pangea
required this.selected,
required this.pangeaMessageEvent,
required this.selectedDisplayLang,
required this.immersionMode,
required this.definitions,
// Pangea#
}) : super(key: key);
void _verifyOrRequestKey(BuildContext context) async {
@ -50,13 +67,15 @@ class MessageContent extends StatelessWidget {
);
return;
}
final client = Matrix.of(context).client;
if (client.isUnknownSession && client.encryption!.crossSigning.enabled) {
final success = await BootstrapDialog(
client: Matrix.of(context).client,
).show(context);
if (success != true) return;
}
// #Pangea
// final client = Matrix.of(context).client;
// if (client.isUnknownSession && client.encryption!.crossSigning.enabled) {
// final success = await BootstrapDialog(
// client: Matrix.of(context).client,
// ).show(context);
// if (success != true) return;
// }
// Pangea#
event.requestKey();
final sender = event.senderFromMemoryOrFallback;
await showAdaptiveBottomSheet(
@ -231,12 +250,41 @@ class MessageContent extends StatelessWidget {
final bigEmotes = event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
// #Pangea
final messageTextStyle = TextStyle(
color: textColor,
fontSize: bigEmotes ? fontSize * 3 : fontSize,
decoration: event.redacted ? TextDecoration.lineThrough : null,
height: 1.3,
);
if (pangeaMessageEvent.showRichText) {
return PangeaRichText(
existingStyle: messageTextStyle,
selected: selected,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode: immersionMode,
definitions: definitions,
selectedDisplayLang: selectedDisplayLang,
);
}
//Pangea#
return FutureBuilder<String>(
future: event.calcLocalizedBody(
MatrixLocals(L10n.of(context)!),
hideReply: true,
),
builder: (context, snapshot) {
// #Pangea
if (!snapshot.hasData) {
return Text(
event.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
hideReply: true,
),
style: messageTextStyle,
);
}
// Pangea#
return Linkify(
text: snapshot.data ??
event.calcLocalizedBodyFallback(

View file

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class MessageDownloadContent extends StatelessWidget {
final Event event;

View file

@ -1,15 +1,13 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
class MessageReactions extends StatelessWidget {
final Event event;

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';

View file

@ -1,9 +1,11 @@
import 'dart:io';
import 'package:chewie/chewie.dart';
import 'package:fluffychat/pages/chat/events/image_bubble.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -11,9 +13,6 @@ import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html;
import 'package:video_player/video_player.dart';
import 'package:fluffychat/pages/chat/events/image_bubble.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import '../../../utils/error_reporter.dart';
class EventVideoPlayer extends StatefulWidget {

View file

@ -1,19 +1,15 @@
import 'package:emojis/emoji.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:emojis/emoji.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:matrix/matrix.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:slugify/slugify.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import '../../widgets/avatar.dart';
import '../../widgets/matrix.dart';
import 'command_hints.dart';
class InputBar extends StatelessWidget {
final Room room;
@ -24,7 +20,10 @@ class InputBar extends StatelessWidget {
final ValueChanged<String>? onSubmitted;
final ValueChanged<Uint8List?>? onSubmitImage;
final FocusNode? focusNode;
final TextEditingController? controller;
// #Pangea
// final TextEditingController? controller;
final PangeaTextController? controller;
// Pangea#
final InputDecoration? decoration;
final ValueChanged<String>? onChanged;
final bool? autofocus;
@ -48,14 +47,19 @@ class InputBar extends StatelessWidget {
}) : super(key: key);
List<Map<String, String?>> getSuggestions(String text) {
if (controller!.selection.baseOffset !=
controller!.selection.extentOffset ||
controller!.selection.baseOffset < 0) {
return []; // no entries if there is selected text
}
// #Pangea
final List<Map<String, String?>> ret = <Map<String, String?>>[];
// if (controller!.selection.baseOffset !=
// controller!.selection.extentOffset ||
// controller!.selection.baseOffset < 0) {
// return []; // no entries if there is selected text
// }
// Pangea#
final searchText =
controller!.text.substring(0, controller!.selection.baseOffset);
final List<Map<String, String?>> ret = <Map<String, String?>>[];
// #Pangea
// final List<Map<String, String?>> ret = <Map<String, String?>>[];
// Pangea#
const maxResults = 30;
final commandMatch = RegExp(r'^/(\w*)$').firstMatch(searchText);
@ -221,104 +225,106 @@ class InputBar extends StatelessWidget {
Map<String, String?> suggestion,
Client? client,
) {
const size = 30.0;
const padding = EdgeInsets.all(4.0);
if (suggestion['type'] == 'command') {
final command = suggestion['name']!;
final hint = commandHint(L10n.of(context)!, command);
return Tooltip(
message: hint,
waitDuration: const Duration(days: 1), // don't show on hover
child: Container(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'/$command',
style: const TextStyle(fontFamily: 'monospace'),
),
Text(
hint,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
);
}
if (suggestion['type'] == 'emoji') {
final label = suggestion['label']!;
return Tooltip(
message: label,
waitDuration: const Duration(days: 1), // don't show on hover
child: Container(
padding: padding,
child: Text(label, style: const TextStyle(fontFamily: 'monospace')),
),
);
}
if (suggestion['type'] == 'emote') {
return Container(
padding: padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
MxcImage(
// ensure proper ordering ...
key: ValueKey(suggestion['name']),
uri: suggestion['mxc'] is String
? Uri.parse(suggestion['mxc'] ?? '')
: null,
width: size,
height: size,
),
const SizedBox(width: 6),
Text(suggestion['name']!),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Opacity(
opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5,
child: suggestion['pack_avatar_url'] != null
? Avatar(
mxContent: Uri.tryParse(
suggestion.tryGet<String>('pack_avatar_url') ?? '',
),
name: suggestion.tryGet<String>('pack_display_name'),
size: size * 0.9,
client: client,
)
: Text(suggestion['pack_display_name']!),
),
),
),
],
),
);
}
if (suggestion['type'] == 'user' || suggestion['type'] == 'room') {
final url = Uri.parse(suggestion['avatar_url'] ?? '');
return Container(
padding: padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Avatar(
mxContent: url,
name: suggestion.tryGet<String>('displayname') ??
suggestion.tryGet<String>('mxid'),
size: size,
client: client,
),
const SizedBox(width: 6),
Text(suggestion['displayname'] ?? suggestion['mxid']!),
],
),
);
}
// #Pangea
// const size = 30.0;
// const padding = EdgeInsets.all(4.0);
// if (suggestion['type'] == 'command') {
// final command = suggestion['name']!;
// final hint = commandHint(L10n.of(context)!, command);
// return Tooltip(
// message: hint,
// waitDuration: const Duration(days: 1), // don't show on hover
// child: Container(
// padding: padding,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// '/$command',
// style: const TextStyle(fontFamily: 'monospace'),
// ),
// Text(
// hint,
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// style: Theme.of(context).textTheme.bodySmall,
// ),
// ],
// ),
// ),
// );
// }
// if (suggestion['type'] == 'emoji') {
// final label = suggestion['label']!;
// return Tooltip(
// message: label,
// waitDuration: const Duration(days: 1), // don't show on hover
// child: Container(
// padding: padding,
// child: Text(label, style: const TextStyle(fontFamily: 'monospace')),
// ),
// );
// }
// if (suggestion['type'] == 'emote') {
// return Container(
// padding: padding,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// MxcImage(
// // ensure proper ordering ...
// key: ValueKey(suggestion['name']),
// uri: suggestion['mxc'] is String
// ? Uri.parse(suggestion['mxc'] ?? '')
// : null,
// width: size,
// height: size,
// ),
// const SizedBox(width: 6),
// Text(suggestion['name']!),
// Expanded(
// child: Align(
// alignment: Alignment.centerRight,
// child: Opacity(
// opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5,
// child: suggestion['pack_avatar_url'] != null
// ? Avatar(
// mxContent: Uri.tryParse(
// suggestion.tryGet<String>('pack_avatar_url') ?? '',
// ),
// name: suggestion.tryGet<String>('pack_display_name'),
// size: size * 0.9,
// client: client,
// )
// : Text(suggestion['pack_display_name']!),
// ),
// ),
// ),
// ],
// ),
// );
// }
// if (suggestion['type'] == 'user' || suggestion['type'] == 'room') {
// final url = Uri.parse(suggestion['avatar_url'] ?? '');
// return Container(
// padding: padding,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Avatar(
// mxContent: url,
// name: suggestion.tryGet<String>('displayname') ??
// suggestion.tryGet<String>('mxid'),
// size: size,
// client: client,
// ),
// const SizedBox(width: 6),
// Text(suggestion['displayname'] ?? suggestion['mxid']!),
// ],
// ),
// );
// }
// Pangea#
return const SizedBox.shrink();
}
@ -445,45 +451,61 @@ class InputBar extends StatelessWidget {
},
),
},
child: TypeAheadField<Map<String, String?>>(
direction: AxisDirection.up,
hideOnEmpty: true,
hideOnLoading: true,
keepSuggestionsOnSuggestionSelected: true,
debounceDuration: const Duration(milliseconds: 50),
// show suggestions after 50ms idle time (default is 300)
textFieldConfiguration: TextFieldConfiguration(
minLines: minLines,
maxLines: maxLines,
keyboardType: keyboardType!,
textInputAction: textInputAction,
autofocus: autofocus!,
onSubmitted: (text) {
// fix for library for now
// it sets the types for the callback incorrectly
onSubmitted!(text);
},
controller: controller,
decoration: decoration!,
focusNode: focusNode,
onChanged: (text) {
// fix for the library for now
// it sets the types for the callback incorrectly
onChanged!(text);
},
textCapitalization: TextCapitalization.sentences,
// #Pangea
child: CompositedTransformTarget(
link: controller!.choreographer.inputLayerLinkAndKey.link,
// Pangea#
child: TypeAheadField<Map<String, String?>>(
direction: AxisDirection.up,
hideOnEmpty: true,
hideOnLoading: true,
keepSuggestionsOnSuggestionSelected: true,
debounceDuration: const Duration(milliseconds: 50),
// show suggestions after 50ms idle time (default is 300)
// #Pangea
key: controller!.choreographer.inputLayerLinkAndKey.key,
// Pangea#
textFieldConfiguration: TextFieldConfiguration(
minLines: minLines,
maxLines: maxLines,
keyboardType: keyboardType!,
textInputAction: textInputAction,
autofocus: autofocus!,
onSubmitted: (text) {
// fix for library for now
// it sets the types for the callback incorrectly
onSubmitted!(text);
},
// #Pangea
onTap: () {
controller!.onInputTap(
context,
fNode: focusNode!,
);
},
// Pangea#
controller: controller,
decoration: decoration!,
focusNode: focusNode,
onChanged: (text) {
// fix for the library for now
// it sets the types for the callback incorrectly
onChanged!(text);
},
textCapitalization: TextCapitalization.sentences,
),
suggestionsCallback: getSuggestions,
itemBuilder: (c, s) =>
buildSuggestion(c, s, Matrix.of(context).client),
onSuggestionSelected: (Map<String, String?> suggestion) =>
insertSuggestion(context, suggestion),
errorBuilder: (BuildContext context, Object? error) =>
const SizedBox.shrink(),
loadingBuilder: (BuildContext context) => const SizedBox.shrink(),
// fix loading briefly flickering a dark box
noItemsFoundBuilder: (BuildContext context) => const SizedBox
.shrink(), // fix loading briefly showing no suggestions
),
suggestionsCallback: getSuggestions,
itemBuilder: (c, s) =>
buildSuggestion(c, s, Matrix.of(context).client),
onSuggestionSelected: (Map<String, String?> suggestion) =>
insertSuggestion(context, suggestion),
errorBuilder: (BuildContext context, Object? error) =>
const SizedBox.shrink(),
loadingBuilder: (BuildContext context) => const SizedBox.shrink(),
// fix loading briefly flickering a dark box
noItemsFoundBuilder: (BuildContext context) => const SizedBox
.shrink(), // fix loading briefly showing no suggestions
),
),
);

View file

@ -1,16 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
class PinnedEvents extends StatelessWidget {
final ChatController controller;

View file

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:emoji_proposal/emoji_proposal.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/app_emojis.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
class ReactionsPicker extends StatelessWidget {

View file

@ -1,15 +1,14 @@
import 'dart:async';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'events/audio_player.dart';
class RecordingDialog extends StatefulWidget {
@ -46,7 +45,16 @@ class RecordingDialogState extends State<RecordingDialog> {
return;
}
await WakelockPlus.enable();
// We try to pick Opus where supported, since that is a codec optimized
// for speech as well as what the voice messages MSC uses.
final audioCodec =
(await _audioRecorder.isEncoderSupported(AudioEncoder.opus))
? AudioEncoder.opus
: AudioEncoder.aacLc;
await _audioRecorder.start(
encoder: audioCodec,
path: _recordedPath,
bitRate: bitRate,
samplingRate: samplingRate,

View file

@ -1,11 +1,10 @@
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/size_string.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/size_string.dart';
import '../../utils/resize_image.dart';
class SendFileDialog extends StatefulWidget {

View file

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:url_launcher/link.dart';
import 'edit_widgets_dialog.dart';
class WidgetsBottomSheet extends StatelessWidget {
final Room room;
const WidgetsBottomSheet({Key? key, required this.room}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
if (index == room.widgets.length) {
return ListTile(
leading: const Icon(Icons.edit),
title: Text(L10n.of(context)!.editWidgets),
onTap: () {
Navigator.of(context).pop();
showDialog(
context: context,
builder: (context) => EditWidgetsDialog(room: room),
useRootNavigator: false,
);
},
);
}
final widget = room.widgets[index];
return Link(
builder: (context, callback) {
return ListTile(
title: Text(widget.name ?? widget.url),
subtitle: Text(widget.type),
onTap: callback,
);
},
target: LinkTarget.blank,
uri: Uri.parse(widget.url),
);
},
itemCount: room.widgets.length + 1,
);
}
}

View file

@ -1,9 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/pages/chat_details/chat_details_view.dart';
import 'package:fluffychat/pages/settings/settings.dart';
import 'package:fluffychat/pangea/utils/set_class_name.dart';
import 'package:fluffychat/pangea/utils/set_class_topic.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
@ -11,13 +19,6 @@ import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_details/chat_details_view.dart';
import 'package:fluffychat/pages/settings/settings.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
enum AliasActions { copy, delete, setCanonical }
class ChatDetails extends StatefulWidget {
@ -40,34 +41,42 @@ class ChatDetailsController extends State<ChatDetails> {
String? get roomId => widget.roomId;
void setDisplaynameAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog(
context: context,
title: L10n.of(context)!.changeTheNameOfTheGroup,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
initialText: room.getLocalizedDisplayname(
MatrixLocals(
L10n.of(context)!,
),
),
),
],
);
if (input == null) return;
final success = await showFutureLoadingDialog(
context: context,
future: () => room.setName(input.single),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)),
);
}
}
// #Pangea
final GlobalKey<AddToSpaceState> addToSpaceKey = GlobalKey<AddToSpaceState>();
bool displayAddStudentOptions = false;
void toggleAddStudentOptions() =>
setState(() => displayAddStudentOptions = !displayAddStudentOptions);
void setDisplaynameAction() => setClassDisplayname(context, roomId);
// void setDisplaynameAction() async {
// final room = Matrix.of(context).client.getRoomById(roomId!)!;
// final input = await showTextInputDialog(
// context: context,
// title: L10n.of(context)!.changeTheNameOfTheGroup,
// okLabel: L10n.of(context)!.ok,
// cancelLabel: L10n.of(context)!.cancel,
// textFields: [
// DialogTextField(
// initialText: room.getLocalizedDisplayname(
// MatrixLocals(
// L10n.of(context)!,
// ),
// ),
// ),
// ],
// );
// if (input == null) return;
// final success = await showFutureLoadingDialog(
// context: context,
// future: () => room.setName(input.single),
// );
// if (success.error == null) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)),
// );
// }
// }
// Pangea#
void editAliases() async {
final room = Matrix.of(context).client.getRoomById(roomId!);
@ -103,6 +112,9 @@ class ChatDetailsController extends State<ChatDetails> {
return setAliasAction();
}
final select = await showConfirmationDialog(
// #Pangea
useRootNavigator: false,
// Pangea#
context: context,
title: L10n.of(context)!.editRoomAliases,
actions: [
@ -175,6 +187,9 @@ class ChatDetailsController extends State<ChatDetails> {
final domain = room.client.userID!.domain;
final input = await showTextInputDialog(
// #Pangea
useRootNavigator: false,
// Pangea#
context: context,
title: L10n.of(context)!.setInvitationLink,
okLabel: L10n.of(context)!.ok,
@ -198,32 +213,35 @@ class ChatDetailsController extends State<ChatDetails> {
void setTopicAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog(
context: context,
title: L10n.of(context)!.setChatDescription,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
hintText: L10n.of(context)!.noChatDescriptionYet,
initialText: room.topic,
minLines: 4,
maxLines: 8,
),
],
);
if (input == null) return;
final success = await showFutureLoadingDialog(
context: context,
future: () => room.setDescription(input.single),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.chatDescriptionHasBeenChanged),
),
);
}
// #Pangea
setClassTopic(room, context);
// final input = await showTextInputDialog(
// context: context,
// title: L10n.of(context)!.setChatDescription,
// okLabel: L10n.of(context)!.ok,
// cancelLabel: L10n.of(context)!.cancel,
// textFields: [
// DialogTextField(
// hintText: L10n.of(context)!.noChatDescriptionYet,
// initialText: room.topic,
// minLines: 4,
// maxLines: 8,
// ),
// ],
// );
// if (input == null) return;
// final success = await showFutureLoadingDialog(
// context: context,
// future: () => room.setDescription(input.single),
// );
// if (success.error == null) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(L10n.of(context)!.chatDescriptionHasBeenChanged),
// ),
// );
// }
// Pangea#
}
void setGuestAccess() async {
@ -400,4 +418,10 @@ class ChatDetailsController extends State<ChatDetails> {
@override
Widget build(BuildContext context) => ChatDetailsView(this);
// #Pangea
bool showEditNameIcon = false;
void hoverEditNameIcon(bool hovering) =>
setState(() => showEditNameIcon = !showEditNameIcon);
// Pangea#
}

View file

@ -1,20 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pages/chat_details/participant_list_item.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/pages/class_settings/class_name_header.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_description_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_details_toggle_add_students_tile.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_invitation_buttons.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_name_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
import 'package:fluffychat/pangea/utils/lock_room.dart';
import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/pangea/widgets/space/class_settings.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/url_launcher.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
class ChatDetailsView extends StatelessWidget {
final ChatDetailsController controller;
@ -57,18 +65,27 @@ class ChatDetailsView extends StatelessWidget {
leading: const Center(child: BackButton()),
elevation: Theme.of(context).appBarTheme.elevation,
actions: <Widget>[
if (room.canonicalAlias.isNotEmpty)
IconButton(
tooltip: L10n.of(context)!.share,
icon: Icon(Icons.adaptive.share_outlined),
onPressed: () => FluffyShare.share(
AppConfig.inviteLinkPrefix + room.canonicalAlias,
context,
),
),
ChatSettingsPopupMenu(room, false),
// #Pangea
// if (room.canonicalAlias.isNotEmpty)
// IconButton(
// tooltip: L10n.of(context)!.share,
// icon: Icon(Icons.adaptive.share_outlined),
// onPressed: () => FluffyShare.share(
// AppConfig.inviteLinkPrefix + room.canonicalAlias,
// context,
// ),
// ),
if (!room.isSpace)
// Pangea#
ChatSettingsPopupMenu(room, false),
],
title: Text(L10n.of(context)!.chatDetails),
// #Pangea
title: ClassNameHeader(
controller: controller,
room: room,
),
// title: Text(L10n.of(context)!.chatDetails),
// Pangea#
backgroundColor:
Theme.of(context).appBarTheme.backgroundColor,
),
@ -207,162 +224,216 @@ class ChatDetailsView extends StatelessWidget {
height: 1,
color: Theme.of(context).dividerColor,
),
if (!room.canChangeStateEvent(EventTypes.RoomTopic))
// #Pangea
if (room.canSendEvent('m.room.name'))
ClassNameButton(
room: room,
controller: controller,
),
if (room.canSendEvent('m.room.topic'))
ClassDescriptionButton(
room: room,
controller: controller,
),
if ((room.isPangeaClass || room.isExchange) &&
room.isRoomAdmin)
ListTile(
title: Text(
L10n.of(context)!.chatDescription,
L10n.of(context)!.classAnalytics,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
)
else
Padding(
padding: const EdgeInsets.all(16.0),
child: OutlinedButton.icon(
onPressed: controller.setTopicAction,
label: Text(L10n.of(context)!.setChatDescription),
icon: const Icon(Icons.edit_outlined),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
child: SelectableLinkify(
text: room.topic.isEmpty
? L10n.of(context)!.noChatDescriptionYet
: room.topic,
options: const LinkifyOptions(humanize: false),
linkStyle:
const TextStyle(color: Colors.blueAccent),
style: TextStyle(
fontSize: 14,
fontStyle: room.topic.isEmpty
? FontStyle.italic
: FontStyle.normal,
color:
Theme.of(context).textTheme.bodyMedium!.color,
decorationColor:
Theme.of(context).textTheme.bodyMedium!.color,
),
onOpen: (url) =>
UrlLauncher(context, url.url).launchUrl(),
),
),
const SizedBox(height: 16),
Divider(
height: 1,
color: Theme.of(context).dividerColor,
),
if (room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.link_outlined),
),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.editAliases,
title: Text(L10n.of(context)!.editRoomAliases),
subtitle: Text(
(room.canonicalAlias.isNotEmpty)
? room.canonicalAlias
: L10n.of(context)!.none,
),
),
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.insert_emoticon_outlined,
),
),
title: Text(L10n.of(context)!.emoteSettings),
subtitle: Text(L10n.of(context)!.setCustomEmotes),
onTap: controller.goToEmoteSettings,
trailing: const Icon(Icons.chevron_right_outlined),
),
if (!room.isDirectChat)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.shield_outlined),
),
title: Text(
L10n.of(context)!.whoIsAllowedToJoinThisGroup,
),
trailing: room.canChangeJoinRules
? const Icon(Icons.chevron_right_outlined)
: null,
subtitle: Text(
room.joinRules?.getLocalizedString(
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
),
onTap: room.canChangeJoinRules
? controller.setJoinRules
: null,
),
if (!room.isDirectChat)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.visibility_outlined),
),
trailing: room.canChangeHistoryVisibility
? const Icon(Icons.chevron_right_outlined)
: null,
title: Text(
L10n.of(context)!.visibilityOfTheChatHistory,
),
subtitle: Text(
room.historyVisibility?.getLocalizedString(
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
),
onTap: room.canChangeHistoryVisibility
? controller.setHistoryVisibility
: null,
),
if (room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.person_add_alt_1_outlined,
Icons.analytics_outlined,
),
),
trailing: room.canChangeGuestAccess
? const Icon(Icons.chevron_right_outlined)
: null,
title: Text(
L10n.of(context)!.areGuestsAllowedToJoin,
onTap: () => context.go(
'/rooms/analytics/${room.id}',
),
subtitle: Text(
room.guestAccess.getLocalizedString(
MatrixLocals(L10n.of(context)!),
),
),
onTap: room.canChangeGuestAccess
? controller.setGuestAccess
: null,
),
if (!room.isDirectChat)
if (room.classSettings != null && room.isRoomAdmin)
ClassSettings(
roomId: controller.roomId,
startOpen: false,
),
if (room.pangeaRoomRules != null && room.isRoomAdmin)
RoomRulesEditor(
roomId: controller.roomId,
startOpen: false,
),
// if (!room.canChangeStateEvent(EventTypes.RoomTopic))
// ListTile(
// title: Text(
// L10n.of(context)!.chatDescription,
// style: TextStyle(
// color: Theme.of(context).colorScheme.secondary,
// fontWeight: FontWeight.bold,
// ),
// ),
// )
// else
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: OutlinedButton.icon(
// onPressed: controller.setTopicAction,
// label: Text(L10n.of(context)!.setChatDescription),
// icon: const Icon(Icons.edit_outlined),
// ),
// ),
// Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 16.0,
// ),
// child: SelectableLinkify(
// text: room.topic.isEmpty
// ? L10n.of(context)!.noChatDescriptionYet
// : room.topic,
// options: const LinkifyOptions(humanize: false),
// linkStyle:
// const TextStyle(color: Colors.blueAccent),
// style: TextStyle(
// fontSize: 14,
// fontStyle: room.topic.isEmpty
// ? FontStyle.italic
// : FontStyle.normal,
// color:
// Theme.of(context).textTheme.bodyMedium!.color,
// decorationColor:
// Theme.of(context).textTheme.bodyMedium!.color,
// ),
// onOpen: (url) =>
// UrlLauncher(context, url.url).launchUrl(),
// ),
// ),
// const SizedBox(height: 16),
// Divider(
// height: 1,
// color: Theme.of(context).dividerColor,
// ),
// if (room.joinRules == JoinRules.public)
// ListTile(
// leading: CircleAvatar(
// backgroundColor:
// Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: iconColor,
// child: const Icon(Icons.link_outlined),
// ),
// trailing: const Icon(Icons.chevron_right_outlined),
// onTap: controller.editAliases,
// title: Text(L10n.of(context)!.editRoomAliases),
// subtitle: Text(
// (room.canonicalAlias.isNotEmpty)
// ? room.canonicalAlias
// : L10n.of(context)!.none,
// ),
// ),
// ListTile(
// leading: CircleAvatar(
// backgroundColor:
// Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: iconColor,
// child: const Icon(
// Icons.insert_emoticon_outlined,
// ),
// ),
// title: Text(L10n.of(context)!.emoteSettings),
// subtitle: Text(L10n.of(context)!.setCustomEmotes),
// onTap: controller.goToEmoteSettings,
// trailing: const Icon(Icons.chevron_right_outlined),
// ),
// if (!room.isDirectChat)
// ListTile(
// leading: CircleAvatar(
// backgroundColor:
// Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: iconColor,
// child: const Icon(Icons.shield_outlined),
// ),
// title: Text(
// L10n.of(context)!.whoIsAllowedToJoinThisGroup,
// ),
// trailing: room.canChangeJoinRules
// ? const Icon(Icons.chevron_right_outlined)
// : null,
// subtitle: Text(
// room.joinRules?.getLocalizedString(
// MatrixLocals(L10n.of(context)!),
// ) ??
// L10n.of(context)!.none,
// ),
// onTap: room.canChangeJoinRules
// ? controller.setJoinRules
// : null,
// ),
// if (!room.isDirectChat)
// ListTile(
// leading: CircleAvatar(
// backgroundColor:
// Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: iconColor,
// child: const Icon(Icons.visibility_outlined),
// ),
// trailing: room.canChangeHistoryVisibility
// ? const Icon(Icons.chevron_right_outlined)
// : null,
// title: Text(
// L10n.of(context)!.visibilityOfTheChatHistory,
// ),
// subtitle: Text(
// room.historyVisibility?.getLocalizedString(
// MatrixLocals(L10n.of(context)!),
// ) ??
// L10n.of(context)!.none,
// ),
// onTap: room.canChangeHistoryVisibility
// ? controller.setHistoryVisibility
// : null,
// ),
// if (room.joinRules == JoinRules.public)
// ListTile(
// leading: CircleAvatar(
// backgroundColor:
// Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: iconColor,
// child: const Icon(
// Icons.person_add_alt_1_outlined,
// ),
// ),
// trailing: room.canChangeGuestAccess
// ? const Icon(Icons.chevron_right_outlined)
// : null,
// title: Text(
// L10n.of(context)!.areGuestsAllowedToJoin,
// ),
// subtitle: Text(
// room.guestAccess.getLocalizedString(
// MatrixLocals(L10n.of(context)!),
// ),
// ),
// onTap: room.canChangeGuestAccess
// ? controller.setGuestAccess
// : null,
// ),
// if (!room.isDirectChat)
if (!room.isDirectChat && !room.isSpace)
// Pangea#
ListTile(
title: Text(L10n.of(context)!.chatPermissions),
// #Pangea
// title: Text(L10n.of(context)!.chatPermissions),
title: Text(
L10n.of(context)!.editChatPermissions,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
// Pangea#
subtitle: Text(
L10n.of(context)!.whoCanPerformWhichAction,
),
@ -374,7 +445,9 @@ class ChatDetailsView extends StatelessWidget {
Icons.edit_attributes_outlined,
),
),
trailing: const Icon(Icons.chevron_right_outlined),
// #Pangea
// trailing: const Icon(Icons.chevron_right_outlined),
// Pangea#
onTap: () => context
.push('/rooms/${room.id}/details/permissions'),
),
@ -382,6 +455,63 @@ class ChatDetailsView extends StatelessWidget {
height: 1,
color: Theme.of(context).dividerColor,
),
// #Pangea
if (room.canInvite)
ListTile(
title: Text(
room.isSpace
? L10n.of(context)!.inviteUsersFromPangea
: L10n.of(context)!.inviteStudentByUserName,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor:
Theme.of(context).textTheme.bodyLarge!.color,
child: const Icon(
Icons.add,
),
),
onTap: () => context.go('/rooms/${room.id}/invite'),
),
if (room.showClassEditOptions && room.isSpace)
SpaceDetailsToggleAddStudentsTile(
controller: controller,
),
if (controller.displayAddStudentOptions &&
room.showClassEditOptions)
ClassInvitationButtons(roomId: controller.roomId!),
if (!room.isPangeaClass)
AddToSpaceToggles(
roomId: room.id,
key: controller.addToSpaceKey,
startOpen: false,
mode: room.isExchange
? AddToClassMode.exchange
: AddToClassMode.chat,
),
const Divider(height: 1),
if (room.isRoomAdmin)
SwitchListTile.adaptive(
activeColor: AppConfig.activeToggleColor,
title: room.isSpace
? Text(L10n.of(context)!.lockSpace)
: Text(L10n.of(context)!.lockChat),
value: room.locked,
onChanged: (value) => showFutureLoadingDialog(
context: context,
future: () => toggleLockRoom(
room,
Matrix.of(context).client,
),
),
),
const Divider(height: 1),
// Pangea#
ListTile(
title: Text(
L10n.of(context)!.countParticipants(
@ -393,18 +523,20 @@ class ChatDetailsView extends StatelessWidget {
),
),
),
if (!room.isDirectChat && room.canInvite)
ListTile(
title: Text(L10n.of(context)!.inviteContact),
leading: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
radius: Avatar.defaultSize / 2,
child: const Icon(Icons.add_outlined),
),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: () => context.go('/rooms/${room.id}/invite'),
),
// #Pangea
// if (!room.isDirectChat && room.canInvite)
// ListTile(
// title: Text(L10n.of(context)!.inviteContact),
// leading: CircleAvatar(
// backgroundColor: Theme.of(context).primaryColor,
// foregroundColor: Colors.white,
// radius: Avatar.defaultSize / 2,
// child: const Icon(Icons.add_outlined),
// ),
// trailing: const Icon(Icons.chevron_right_outlined),
// onTap: () => context.go('/rooms/${room.id}/invite'),
// ),
// Pangea#
],
)
: i < members.length + 1

View file

@ -1,16 +1,16 @@
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import '../../widgets/avatar.dart';
import '../user_bottom_sheet/user_bottom_sheet.dart';
class ParticipantListItem extends StatelessWidget {
final User user;
const ParticipantListItem(this.user, {Key? key}) : super(key: key);
const ParticipantListItem(this.user, {super.key});
@override
Widget build(BuildContext context) {
@ -26,16 +26,33 @@ class ParticipantListItem extends StatelessWidget {
? L10n.of(context)!.moderator
: '';
// #Pangea
if (user.id == BotName.byEnvironment) {
return const SizedBox();
}
// Pangea#
return Opacity(
opacity: user.membership == Membership.join ? 1 : 0.5,
//#Pangea
// opacity: user.membership == Membership.join? 1 : 0.5,
opacity:
user.membership == Membership.join && user.id != BotName.byEnvironment
? 1
: 0.5,
//Pangea#
child: ListTile(
onTap: () => showAdaptiveBottomSheet(
context: context,
builder: (c) => UserBottomSheet(
user: user,
outerContext: context,
),
),
//#Pangea
// onTap: () => showAdaptiveBottomSheet(
onTap: user.id == BotName.byEnvironment
? null
: () => showAdaptiveBottomSheet(
//Pangea#
context: context,
builder: (c) => UserBottomSheet(
user: user,
outerContext: context,
),
),
title: Row(
children: <Widget>[
Expanded(
@ -52,18 +69,23 @@ class ParticipantListItem extends StatelessWidget {
),
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
// #Pangea
// color: Theme.of(context).colorScheme.primaryContainer,
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.primary,
),
// border: Border.all(
// color: Theme.of(context).colorScheme.primary,
// ),
// Pangea#
),
child: Text(
permissionBatch,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary,
),
// #Pangea
// style: TextStyle(
// fontSize: 14,
// color: Theme.of(context).colorScheme.primary,
// ),
// Pangea#
),
),
membershipBatch[user.membership]!.isEmpty

View file

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings_view.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings_view.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../key_verification/key_verification_dialog.dart';
class ChatEncryptionSettings extends StatefulWidget {

View file

@ -1,21 +1,17 @@
import 'package:flutter/cupertino.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
import 'package:fluffychat/utils/beautify_string_extension.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
import 'package:fluffychat/utils/beautify_string_extension.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
class ChatEncryptionSettingsView extends StatelessWidget {
final ChatEncryptionSettingsController controller;
const ChatEncryptionSettingsView(this.controller, {Key? key})
: super(key: key);
const ChatEncryptionSettingsView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
@ -54,11 +50,13 @@ class ChatEncryptionSettingsView extends StatelessWidget {
value: room.encrypted,
onChanged: controller.enableEncryption,
),
Icon(
CupertinoIcons.lock_shield,
size: 128,
color: Theme.of(context).colorScheme.onInverseSurface,
),
// #Pangea
// Icon(
// CupertinoIcons.lock_shield,
// size: 128,
// color: Theme.of(context).colorScheme.onInverseSurface,
// ),
// Pangea#
const Divider(),
if (room.isDirectChat)
Padding(

View file

@ -1,11 +1,27 @@
import 'dart:async';
import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
import 'package:fluffychat/pages/settings_security/settings_security.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/utils/famedlysdk_store.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/tor_stub.dart'
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
@ -13,25 +29,12 @@ import 'package:matrix/matrix.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:uni_links/uni_links.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
import 'package:fluffychat/pages/settings_security/settings_security.dart';
import 'package:fluffychat/utils/famedlysdk_store.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import '../../../utils/account_bundles.dart';
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
import '../../utils/url_launcher.dart';
import '../../utils/voip/callkeep_manager.dart';
import '../../widgets/fluffy_chat_app.dart';
import '../../widgets/matrix.dart';
import '../bootstrap/bootstrap_dialog.dart';
import 'package:fluffychat/utils/tor_stub.dart'
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
enum SelectMode {
normal,
@ -61,10 +64,10 @@ class ChatList extends StatefulWidget {
final String? activeChat;
const ChatList({
Key? key,
super.key,
this.displayNavigationRail = false,
required this.activeChat,
}) : super(key: key);
});
@override
ChatListController createState() => ChatListController();
@ -87,6 +90,9 @@ class ChatListController extends State<ChatList>
void resetActiveSpaceId() {
setState(() {
activeSpaceId = null;
//#Pangea
context.go("/rooms");
//Pangea#
});
}
@ -129,6 +135,9 @@ class ChatListController extends State<ChatList>
void onDestinationSelected(int? i) {
setState(() {
// #Pangea
debugPrint('onDestinationSelected $i');
// Pangea#
activeFilter = getActiveFilterByDestination(i);
});
}
@ -140,23 +149,63 @@ class ChatListController extends State<ChatList>
bool Function(Room) getRoomFilterByActiveFilter(ActiveFilter activeFilter) {
switch (activeFilter) {
case ActiveFilter.allChats:
return (room) => !room.isSpace && !room.isStoryRoom;
return (room) =>
!room.isSpace &&
!room.isStoryRoom
// #Pangea
&&
!room.isAnalyticsRoom;
// Pangea#
case ActiveFilter.groups:
return (room) =>
!room.isSpace && !room.isDirectChat && !room.isStoryRoom;
!room.isSpace &&
!room.isDirectChat &&
!room.isStoryRoom
// #Pangea
&&
!room.isAnalyticsRoom;
// Pangea#
case ActiveFilter.messages:
return (room) =>
!room.isSpace && room.isDirectChat && !room.isStoryRoom;
!room.isSpace &&
room.isDirectChat &&
!room.isStoryRoom
// #Pangea
&&
!room.isAnalyticsRoom;
// Pangea#
case ActiveFilter.spaces:
return (r) => r.isSpace;
}
}
List<Room> get filteredRooms => Matrix.of(context)
.client
.rooms
.where(getRoomFilterByActiveFilter(activeFilter))
.toList();
.client
.rooms
.where(
getRoomFilterByActiveFilter(activeFilter),
)
// #Pangea
.sorted((roomA, roomB) {
// put rooms with unread messages at the top of the list
if (roomA.membership == Membership.invite &&
roomB.membership != Membership.invite) {
return -1;
}
if (roomA.membership != Membership.invite &&
roomB.membership == Membership.invite) {
return 1;
}
final bool aUnread = roomA.notificationCount > 0 || roomA.markedUnread;
final bool bUnread = roomB.notificationCount > 0 || roomB.markedUnread;
if (aUnread && !bUnread) return -1;
if (!aUnread && bUnread) return 1;
return 0;
})
// Pangea#
.toList();
bool isSearchMode = false;
Future<QueryPublicRoomsResponse>? publicRoomsResponse;
@ -167,6 +216,9 @@ class ChatListController extends State<ChatList>
bool isSearching = false;
static const String _serverStoreNamespace = 'im.fluffychat.search.server';
//#Pangea
final PangeaController pangeaController = MatrixState.pangeaController;
//Pangea#
void setServer() async {
final newServer = await showTextInputDialog(
@ -372,6 +424,11 @@ class ChatListController extends State<ChatList>
}
}
//#Pangea
StreamSubscription? classStream;
StreamSubscription? _invitedSpaceSubscription;
//Pangea#
@override
void initState() {
_initReceiveSharingIntent();
@ -394,6 +451,40 @@ class ChatListController extends State<ChatList>
_checkTorBrowser();
//#Pangea
classStream = pangeaController.classController.stateStream.listen((event) {
if (event["activeSpaceId"] != null && mounted) {
setActiveSpace(event["activeSpaceId"]);
}
});
_invitedSpaceSubscription = pangeaController
.matrixState.client.onSync.stream
.where((event) => event.rooms?.invite != null)
.listen((event) {
for (final inviteEntry in event.rooms!.invite!.entries) {
if (inviteEntry.value.inviteState == null) continue;
final bool isSpace = inviteEntry.value.inviteState!.any(
(event) =>
event.type == EventTypes.RoomCreate &&
event.content['type'] == 'm.space',
);
if (!isSpace) continue;
final String spaceId = inviteEntry.key;
final Room? space = pangeaController.matrixState.client.getRoomById(
spaceId,
);
if (space != null) {
chatListHandleSpaceTap(
context,
this,
space,
);
}
}
});
//Pangea#
super.initState();
}
@ -402,6 +493,10 @@ class ChatListController extends State<ChatList>
_intentDataStreamSubscription?.cancel();
_intentFileStreamSubscription?.cancel();
_intentUriStreamSubscription?.cancel();
//#Pangea
classStream?.cancel();
_invitedSpaceSubscription?.cancel();
//Pangea#
scrollController.removeListener(_onScroll);
super.dispose();
}
@ -471,6 +566,7 @@ class ChatListController extends State<ChatList>
title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.cancel,
message: L10n.of(context)!.archiveRoomDescription,
) ==
OkCancelResult.ok;
if (!confirmed) return;
@ -526,7 +622,17 @@ class ChatListController extends State<ChatList>
actions: Matrix.of(context)
.client
.rooms
.where((r) => r.isSpace)
.where(
(r) =>
r.isSpace
// #Pangea
&&
selectedRoomIds
.map((id) => Matrix.of(context).client.getRoomById(id))
.where((e) => !(e?.isPangeaClass ?? true))
.every((e) => r.canIAddSpaceChild(e)),
//Pangea#
)
.map(
(space) => AlertDialogAction(
key: space.id,
@ -583,16 +689,35 @@ class ChatListController extends State<ChatList>
await client.accountDataLoading;
if (client.prevBatch == null) {
await client.onSync.stream.first;
// #Pangea
pangeaController.startChatWithBotIfNotPresent();
//Pangea#
// Display first login bootstrap if enabled
if (client.encryption?.keyManager.enabled == true) {
if (await client.encryption?.keyManager.isCached() == false ||
await client.encryption?.crossSigning.isCached() == false ||
client.isUnknownSession && !mounted) {
await BootstrapDialog(client: client).show(context);
}
}
// #Pangea
// if (client.encryption?.keyManager.enabled == true) {
// if (await client.encryption?.keyManager.isCached() == false ||
// await client.encryption?.crossSigning.isCached() == false ||
// client.isUnknownSession && !mounted) {
// await BootstrapDialog(client: client).show(context);
// }
// }
// Pangea#
}
// #Pangea
if (mounted) {
GoogleAnalytics.analyticsUserUpdate(client.userID);
await pangeaController.subscriptionController.initialize();
pangeaController.afterSyncAndFirstLoginInitialization(context);
await pangeaController.inviteBotToExistingSpaces();
} else {
ErrorHandler.logError(
m: "didn't run afterSyncAndFirstLoginInitialization because not mounted",
);
// debugger(when: kDebugMode);
}
// Pangea#
if (!mounted) return;
setState(() {
waitForFirstSync = true;

View file

@ -1,22 +1,19 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pages/chat_list/space_view.dart';
import 'package:fluffychat/pages/chat_list/stories_header.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart';
@ -25,7 +22,7 @@ import 'chat_list_header.dart';
class ChatListViewBody extends StatelessWidget {
final ChatListController controller;
const ChatListViewBody(this.controller, {Key? key}) : super(key: key);
const ChatListViewBody(this.controller, {super.key});
@override
Widget build(BuildContext context) {
@ -71,11 +68,13 @@ class ChatListViewBody extends StatelessWidget {
);
}
final rooms = controller.filteredRooms;
final displayStoriesHeader = {
ActiveFilter.allChats,
ActiveFilter.messages,
}.contains(controller.activeFilter) &&
client.storiesRooms.isNotEmpty;
// Pangea#
// final displayStoriesHeader = {
// ActiveFilter.allChats,
// ActiveFilter.messages,
// }.contains(controller.activeFilter) &&
// client.storiesRooms.isNotEmpty;
// Pangea#
return SafeArea(
child: CustomScrollView(
controller: controller.scrollController,
@ -163,11 +162,13 @@ class ChatListViewBody extends StatelessWidget {
icon: const Icon(Icons.camera_alt_outlined),
),
],
if (displayStoriesHeader)
StoriesHeader(
key: const Key('stories_header'),
filter: controller.searchController.text,
),
// #Pangea
// if (displayStoriesHeader)
// StoriesHeader(
// key: const Key('stories_header'),
// filter: controller.searchController.text,
// ),
// Pangea#
const ConnectionStatusHeader(),
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
@ -196,13 +197,31 @@ class ChatListViewBody extends StatelessWidget {
!controller.isSearchMode) ...[
Padding(
padding: const EdgeInsets.all(32.0),
child: Icon(
CupertinoIcons.chat_bubble_2,
size: 128,
color:
Theme.of(context).colorScheme.onInverseSurface,
// #Pangea
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'private_chat_wallpaper.png',
height: 256,
),
],
),
// child: Icon(
// CupertinoIcons.chat_bubble_2,
// size: 128,
// color:
// Theme.of(context).colorScheme.onInverseSurface,
// ),
// Pangea#
),
// #Pangea
Center(
child: ChatListBodyStartText(
controller: controller,
),
),
// Pangea#
],
],
),
@ -313,8 +332,7 @@ class _SearchItem extends StatelessWidget {
required this.title,
this.avatar,
required this.onPressed,
Key? key,
}) : super(key: key);
});
@override
Widget build(BuildContext context) => InkWell(

View file

@ -1,16 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
import '../../widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
final ChatListController controller;
const ChatListHeader({Key? key, required this.controller}) : super(key: key);
const ChatListHeader({super.key, required this.controller});
@override
Widget build(BuildContext context) {
@ -43,80 +40,85 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
controller.selectedRoomIds.length.toString(),
key: const ValueKey(SelectMode.select),
)
: TextField(
controller: controller.searchController,
focusNode: controller.searchFocusNode,
textInputAction: TextInputAction.search,
onChanged: controller.onSearchEnter,
decoration: InputDecoration(
fillColor: Theme.of(context).colorScheme.secondaryContainer,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(99),
),
hintText: L10n.of(context)!.search,
floatingLabelBehavior: FloatingLabelBehavior.never,
prefixIcon: controller.isSearchMode
? IconButton(
tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelSearch,
color: Theme.of(context).colorScheme.onBackground,
)
: IconButton(
onPressed: controller.startSearch,
icon: Icon(
Icons.search_outlined,
color: Theme.of(context).colorScheme.onBackground,
),
),
suffixIcon: controller.isSearchMode
? controller.isSearching
? const Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 12,
),
child: SizedBox.square(
dimension: 24,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
),
)
: TextButton.icon(
onPressed: controller.setServer,
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(99),
),
textStyle: const TextStyle(fontSize: 12),
),
icon: const Icon(Icons.edit_outlined, size: 16),
label: Text(
controller.searchServer ??
Matrix.of(context)
.client
.homeserver!
.host,
maxLines: 2,
),
)
: SizedBox(
width: 0,
child: ClientChooserButton(controller),
),
),
),
// #Pangea
: ClientChooserButton(controller),
// : TextField(
// controller: controller.searchController,
// focusNode: controller.searchFocusNode,
// textInputAction: TextInputAction.search,
// onChanged: controller.onSearchEnter,
// decoration: InputDecoration(
// fillColor: Theme.of(context).colorScheme.secondaryContainer,
// border: UnderlineInputBorder(
// borderSide: BorderSide.none,
// borderRadius: BorderRadius.circular(99),
// ),
// hintText: L10n.of(context)!.search,
// floatingLabelBehavior: FloatingLabelBehavior.never,
// prefixIcon: controller.isSearchMode
// ? IconButton(
// tooltip: L10n.of(context)!.cancel,
// icon: const Icon(Icons.close_outlined),
// onPressed: controller.cancelSearch,
// color: Theme.of(context).colorScheme.onBackground,
// )
// : IconButton(
// onPressed: controller.startSearch,
// icon: Icon(
// Icons.search_outlined,
// color: Theme.of(context).colorScheme.onBackground,
// ),
// ),
// suffixIcon: controller.isSearchMode
// ? controller.isSearching
// ? const Padding(
// padding: EdgeInsets.symmetric(
// vertical: 10.0,
// horizontal: 12,
// ),
// child: SizedBox.square(
// dimension: 24,
// child: CircularProgressIndicator.adaptive(
// strokeWidth: 2,
// ),
// ),
// )
// : TextButton.icon(
// onPressed: controller.setServer,
// style: TextButton.styleFrom(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(99),
// ),
// textStyle: const TextStyle(fontSize: 12),
// ),
// icon: const Icon(Icons.edit_outlined, size: 16),
// label: Text(
// controller.searchServer ??
// Matrix.of(context)
// .client
// .homeserver!
// .host,
// maxLines: 2,
// ),
// )
// : SizedBox(
// width: 0,
// child: ClientChooserButton(controller),
// ),
// ),
// ),
// Pangea#
actions: selectMode == SelectMode.share
? [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: ClientChooserButton(controller),
),
// #Pangea
// Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 16.0,
// vertical: 8.0,
// ),
// child: ClientChooserButton(controller),
// ),
// Pangea#
]
: selectMode == SelectMode.select
? [
@ -154,7 +156,10 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
onPressed: controller.toggleMuted,
),
IconButton(
icon: const Icon(Icons.delete_outlined),
// #Pangea
// icon: const Icon(Icons.delete_outlined),
icon: const Icon(Icons.archive_outlined),
// Pangea#
tooltip: L10n.of(context)!.archive,
onPressed: controller.archiveAction,
),

View file

@ -1,14 +1,15 @@
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/get_chat_list_item_subtitle.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/room_status_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/room_status_extension.dart';
import '../../config/themes.dart';
import '../../utils/date_time_extension.dart';
import '../../widgets/avatar.dart';
@ -23,6 +24,7 @@ class ChatListItem extends StatelessWidget {
final bool selected;
final void Function()? onTap;
final void Function()? onLongPress;
final void Function()? onForget;
const ChatListItem(
this.room, {
@ -30,8 +32,9 @@ class ChatListItem extends StatelessWidget {
this.selected = false,
this.onTap,
this.onLongPress,
Key? key,
}) : super(key: key);
this.onForget,
super.key,
});
void clickAction(BuildContext context) async {
if (onTap != null) return onTap!();
@ -132,6 +135,7 @@ class ChatListItem extends StatelessWidget {
title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.no,
message: L10n.of(context)!.archiveRoomDescription,
);
if (confirmed == OkCancelResult.cancel) return;
await showFutureLoadingDialog(
@ -189,6 +193,9 @@ class ChatListItem extends StatelessWidget {
mxContent: room.avatar,
name: displayname,
onTap: onLongPress,
//#Pangea
littleIcon: room.roomTypeIcon,
// Pangea#
),
title: Row(
children: <Widget>[
@ -274,17 +281,26 @@ class ChatListItem extends StatelessWidget {
softWrap: false,
)
: FutureBuilder<String>(
future: room.lastEvent?.calcLocalizedBody(
MatrixLocals(L10n.of(context)!),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: !room.isDirectChat ||
room.directChatMatrixID !=
room.lastEvent?.senderId,
) ??
Future.value(L10n.of(context)!.emptyChat),
// #Pangea
// future: room.lastEvent?.calcLocalizedBody(
// MatrixLocals(L10n.of(context)!),
// hideReply: true,
// hideEdit: true,
// plaintextBody: true,
// removeMarkdown: true,
// withSenderNamePrefix: !room.isDirectChat ||
// room.directChatMatrixID !=
// room.lastEvent?.senderId,
// ) ??
// Future.value(L10n.of(context)!.emptyChat),
future: room.lastEvent != null
? GetChatListItemSubtitle().getSubtitle(
context,
room.lastEvent,
MatrixState.pangeaController,
)
: Future.value(L10n.of(context)!.emptyChat),
// Pangea#
builder: (context, snapshot) {
return Text(
room.membership == Membership.invite
@ -361,6 +377,12 @@ class ChatListItem extends StatelessWidget {
],
),
onTap: () => clickAction(context),
trailing: onForget == null
? null
: IconButton(
icon: const Icon(Icons.delete_outlined),
onPressed: onForget,
),
),
),
);

View file

@ -1,18 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:badges/badges.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/navi_rail_item.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/unread_rooms_badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import '../../widgets/matrix.dart';
import 'chat_list_body.dart';
import 'start_chat_fab.dart';
@ -20,7 +19,7 @@ import 'start_chat_fab.dart';
class ChatListView extends StatelessWidget {
final ChatListController controller;
const ChatListView(this.controller, {Key? key}) : super(key: key);
const ChatListView(this.controller, {super.key});
List<NavigationDestination> getNavigationDestinations(BuildContext context) {
final badgePosition = BadgePosition.topEnd(top: -12, end: -8);
@ -39,7 +38,10 @@ class ChatListView extends StatelessWidget {
controller.getRoomFilterByActiveFilter(ActiveFilter.messages),
child: const Icon(Icons.forum),
),
label: L10n.of(context)!.messages,
//#Pangea
// label: L10n.of(context)!.messages,
label: L10n.of(context)!.directChats,
//Pangea#
),
NavigationDestination(
icon: UnreadRoomsBadge(
@ -68,14 +70,24 @@ class ChatListView extends StatelessWidget {
controller.getRoomFilterByActiveFilter(ActiveFilter.allChats),
child: const Icon(Icons.forum),
),
label: L10n.of(context)!.chats,
// #Pangea
// label: L10n.of(context)!.chats,
label: L10n.of(context)!.allChats,
// Pangea#
),
if (controller.spaces.isNotEmpty)
const NavigationDestination(
icon: Icon(Icons.workspaces_outlined),
selectedIcon: Icon(Icons.workspaces),
label: 'Spaces',
// #Pangea
// const NavigationDestination(
// icon: Icon(Icons.workspaces_outlined),
// selectedIcon: Icon(Icons.workspaces),
// label: 'Spaces',
// ),
NavigationDestination(
icon: const Icon(Icons.workspaces_outlined),
selectedIcon: const Icon(Icons.workspaces),
label: L10n.of(context)!.allSpaces,
),
// Pangea#
];
}
@ -115,14 +127,17 @@ class ChatListView extends StatelessWidget {
builder: (context) {
final allSpaces =
client.rooms.where((room) => room.isSpace);
final rootSpaces = allSpaces
.where(
(space) => !allSpaces.any(
(parentSpace) => parentSpace.spaceChildren
.any((child) => child.roomId == space.id),
),
)
.toList();
// #Pangea
// final rootSpaces = allSpaces
// .where(
// (space) => !allSpaces.any(
// (parentSpace) => parentSpace.spaceChildren
// .any((child) => child.roomId == space.id),
// ),
// )
// .toList();
final rootSpaces = allSpaces.toList();
// Pangea#
final destinations = getNavigationDestinations(context);
return SizedBox(
@ -144,13 +159,25 @@ class ChatListView extends StatelessWidget {
final isSelected =
controller.activeFilter == ActiveFilter.spaces &&
rootSpaces[i].id == controller.activeSpaceId;
//#Pangea
final Room? room = Matrix.of(context)
.client
.getRoomById(rootSpaces[i].id);
// Pangea#
return NaviRailItem(
toolTip: rootSpaces[i].getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
),
isSelected: isSelected,
onTap: () =>
controller.setActiveSpace(rootSpaces[i].id),
// #Pangea
// onTap: () =>
// controller.setActiveSpace(rootSpaces[i].id),
onTap: () => chatListHandleSpaceTap(
context,
controller,
rootSpaces[i],
),
// Pangea#
icon: Avatar(
mxContent: rootSpaces[i].avatar,
name: rootSpaces[i].getLocalizedDisplayname(
@ -158,6 +185,9 @@ class ChatListView extends StatelessWidget {
),
size: 32,
fontSize: 12,
// #Pangea
littleIcon: room?.roomTypeIcon,
// Pangea#
),
);
},
@ -193,22 +223,32 @@ class ChatListView extends StatelessWidget {
destinations: getNavigationDestinations(context),
)
: null,
floatingActionButton: KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyN,
},
onKeysPressed: () => context.go('/rooms/newprivatechat'),
helpLabel: L10n.of(context)!.newChat,
child: selectMode == SelectMode.normal &&
!controller.isSearchMode
? StartChatFloatingActionButton(
activeFilter: controller.activeFilter,
roomsIsEmpty: false,
scrolledToTop: controller.scrolledToTop,
)
: const SizedBox.shrink(),
),
// #Pangea
// floatingActionButton: KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.keyN,
// },
// onKeysPressed: () => context.go('/rooms/newprivatechat'),
// helpLabel: L10n.of(context)!.newChat,
// child: selectMode == SelectMode.normal &&
// !controller.isSearchMode
// ? StartChatFloatingActionButton(
// activeFilter: controller.activeFilter,
// roomsIsEmpty: false,
// scrolledToTop: controller.scrolledToTop,
// )
// : const SizedBox.shrink(),
// ),
floatingActionButton: selectMode == SelectMode.normal
? StartChatFloatingActionButton(
activeFilter: controller.activeFilter,
roomsIsEmpty: false,
scrolledToTop: controller.scrolledToTop,
controller: controller,
)
: null,
// Pangea#
),
),
),

View file

@ -1,80 +1,165 @@
import 'dart:developer';
import 'package:fluffychat/pangea/extensions/client_extension.dart';
import 'package:fluffychat/pangea/utils/class_code.dart';
import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart';
import 'package:fluffychat/pangea/utils/logout.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/fluffy_share.dart';
import 'chat_list.dart';
class ClientChooserButton extends StatelessWidget {
final ChatListController controller;
const ClientChooserButton(this.controller, {Key? key}) : super(key: key);
const ClientChooserButton(this.controller, {super.key});
List<PopupMenuEntry<Object>> _bundleMenuItems(BuildContext context) {
final matrix = Matrix.of(context);
final bundles = matrix.accountBundles.keys.toList()
..sort(
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
? 0
: a.isValidMatrixId && !b.isValidMatrixId
? -1
: 1,
);
// #Pangea
// final bundles = matrix.accountBundles.keys.toList()
// ..sort(
// (a, b) => a!.isValidMatrixId == b!.isValidMatrixId
// ? 0
// : a.isValidMatrixId && !b.isValidMatrixId
// ? -1
// : 1,
// );
// Pangea#
return <PopupMenuEntry<Object>>[
// #Pangea
// PopupMenuItem(
// value: SettingsAction.newStory,
// child: Row(
// children: [
// const Icon(Icons.camera_outlined),
// const SizedBox(width: 18),
// Text(L10n.of(context)!.yourStory),
// ],
// ),
// ),
// PopupMenuItem(
// value: SettingsAction.newGroup,
// child: Row(
// children: [
// const Icon(Icons.group_add_outlined),
// const SizedBox(width: 18),
// Text(L10n.of(context)!.createGroup),
// ],
// ),
// ),
PopupMenuItem(
value: SettingsAction.newStory,
value: SettingsAction.joinWithClassCode,
child: Row(
children: [
const Icon(Icons.camera_outlined),
const Icon(Icons.join_full_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.yourStory),
Expanded(child: Text(L10n.of(context)!.joinWithClassCode)),
],
),
),
PopupMenuItem(
value: SettingsAction.newGroup,
enabled: matrix.client.classesAndExchangesImTeaching.isNotEmpty,
value: SettingsAction.classAnalytics,
child: Row(
children: [
const Icon(Icons.group_add_outlined),
const Icon(Icons.analytics_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.createGroup),
Expanded(child: Text(L10n.of(context)!.classAnalytics)),
],
),
),
PopupMenuItem(
value: SettingsAction.newSpace,
enabled: matrix.client.classesImIn.isNotEmpty,
value: SettingsAction.myAnalytics,
child: Row(
children: [
const Icon(Icons.workspaces_outlined),
const Icon(Icons.analytics_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.createNewSpace),
Expanded(child: Text(L10n.of(context)!.myLearning)),
],
),
),
PopupMenuItem(
value: SettingsAction.invite,
value: SettingsAction.newClass,
child: Row(
children: [
Icon(Icons.adaptive.share_outlined),
const Icon(Icons.school),
const SizedBox(width: 18),
Text(L10n.of(context)!.inviteContact),
Expanded(child: Text(L10n.of(context)!.createNewClass)),
],
),
),
// PopupMenuItem(
// value: SettingsAction.newSpace,
// child: Row(
// children: [
// const Icon(Icons.workspaces_outlined),
// const SizedBox(width: 18),
// Text(L10n.of(context)!.createNewSpace),
// ],
// ),
// ),
PopupMenuItem(
value: SettingsAction.newExchange,
child: Row(
children: [
const Icon(Icons.connecting_airports),
const SizedBox(width: 18),
Expanded(child: Text(L10n.of(context)!.newExchange)),
],
),
),
PopupMenuItem(
value: SettingsAction.findAClass,
enabled: false,
child: Row(
children: [
const Icon(Icons.class_outlined),
const SizedBox(width: 18),
Expanded(child: Text(L10n.of(context)!.findAClass)),
],
),
),
if (controller.pangeaController.permissionsController.isUser18())
PopupMenuItem(
value: SettingsAction.findAConversationPartner,
child: Row(
children: [
const Icon(Icons.add_circle_outline),
const SizedBox(width: 18),
Expanded(child: Text(L10n.of(context)!.findALanguagePartner)),
],
),
),
// PopupMenuItem(
// value: SettingsAction.invite,
// child: Row(
// children: [
// Icon(Icons.adaptive.share_outlined),
// const SizedBox(width: 18),
// Text(L10n.of(context)!.inviteContact),
// ],
// ),
// ),
// Pangea#
PopupMenuItem(
value: SettingsAction.archive,
child: Row(
children: [
const Icon(Icons.archive_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.archive),
// #Pangea
// Text(L10n.of(context)!.archive),
Expanded(child: Text(L10n.of(context)!.archive)),
// Pangea#
],
),
),
@ -84,87 +169,99 @@ class ClientChooserButton extends StatelessWidget {
children: [
const Icon(Icons.settings_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.settings),
// #Pangea
// Text(L10n.of(context)!.settings),
Expanded(child: Text(L10n.of(context)!.settings)),
// Pangea#
],
),
),
const PopupMenuItem(
value: null,
child: Divider(height: 1),
),
for (final bundle in bundles) ...[
if (matrix.accountBundles[bundle]!.length != 1 ||
matrix.accountBundles[bundle]!.single!.userID != bundle)
PopupMenuItem(
value: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
bundle!,
style: TextStyle(
color: Theme.of(context).textTheme.titleMedium!.color,
fontSize: 14,
),
),
const Divider(height: 1),
],
),
),
...matrix.accountBundles[bundle]!
.map(
(client) => PopupMenuItem(
value: client,
child: FutureBuilder<Profile?>(
// analyzer does not understand this type cast for error
// handling
//
// ignore: unnecessary_cast
future: (client!.fetchOwnProfile() as Future<Profile?>)
.onError((e, s) => null),
builder: (context, snapshot) => Row(
children: [
Avatar(
mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ??
client.userID!.localpart,
size: 32,
fontSize: 12,
),
const SizedBox(width: 12),
Expanded(
child: Text(
snapshot.data?.displayName ??
client.userID!.localpart!,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 12),
IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: () => controller.editBundlesForAccount(
client.userID,
bundle,
),
),
],
),
),
),
)
.toList(),
],
// #Pangea
// const PopupMenuItem(
// value: null,
// child: Divider(height: 1),
// ),
// for (final bundle in bundles) ...[
// if (matrix.accountBundles[bundle]!.length != 1 ||
// matrix.accountBundles[bundle]!.single!.userID != bundle)
// PopupMenuItem(
// value: null,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// bundle!,
// style: TextStyle(
// color: Theme.of(context).textTheme.titleMedium!.color,
// fontSize: 14,
// ),
// ),
// const Divider(height: 1),
// ],
// ),
// ),
// ...matrix.accountBundles[bundle]!.map(
// (client) => PopupMenuItem(
// value: client,
// child: FutureBuilder<Profile?>(
// // analyzer does not understand this type cast for error
// // handling
// //
// // ignore: unnecessary_cast
// future: (client!.fetchOwnProfile() as Future<Profile?>)
// .onError((e, s) => null),
// builder: (context, snapshot) => Row(
// children: [
// Avatar(
// mxContent: snapshot.data?.avatarUrl,
// name:
// snapshot.data?.displayName ?? client.userID!.localpart,
// size: 32,
// fontSize: 12,
// ),
// const SizedBox(width: 12),
// Expanded(
// child: Text(
// snapshot.data?.displayName ?? client.userID!.localpart!,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// const SizedBox(width: 12),
// IconButton(
// icon: const Icon(Icons.edit_outlined),
// onPressed: () => controller.editBundlesForAccount(
// client.userID,
// bundle,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// PopupMenuItem(
// value: SettingsAction.addAccount,
// child: Row(
// children: [
// const Icon(Icons.person_add_outlined),
// const SizedBox(width: 18),
// Text(L10n.of(context)!.addAccount),
// ],
// ),
// ),
PopupMenuItem(
value: SettingsAction.addAccount,
value: SettingsAction.logout,
child: Row(
children: [
const Icon(Icons.person_add_outlined),
const Icon(Icons.logout_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.addAccount),
Expanded(child: Text(L10n.of(context)!.logout)),
],
),
),
// Pangea#
];
}
@ -214,17 +311,32 @@ class ClientChooserButton extends StatelessWidget {
PopupMenuButton<Object>(
onSelected: (o) => _clientSelected(o, context),
itemBuilder: _bundleMenuItems,
// #Pangea
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(99),
child: Avatar(
mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ??
matrix.client.userID!.localpart,
size: 32,
fontSize: 12,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
),
clipBehavior: Clip.hardEdge,
child: ListTile(
tileColor: Theme.of(context).scaffoldBackgroundColor,
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
leading: const Icon(Icons.settings_outlined),
title: Text(L10n.of(context)!.mainMenu),
),
),
// child: Material(
// color: Colors.transparent,
// borderRadius: BorderRadius.circular(99),
// child: Avatar(
// mxContent: snapshot.data?.avatarUrl,
// name: snapshot.data?.displayName ??
// matrix.client.userID!.localpart,
// size: 32,
// fontSize: 12,
// ),
// ),
// Pangea#
),
],
),
@ -252,26 +364,28 @@ class ClientChooserButton extends StatelessWidget {
controller.setActiveBundle(object);
} else if (object is SettingsAction) {
switch (object) {
case SettingsAction.addAccount:
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context)!.addAccount,
message: L10n.of(context)!.enableMultiAccounts,
okLabel: L10n.of(context)!.next,
cancelLabel: L10n.of(context)!.cancel,
);
if (consent != OkCancelResult.ok) return;
context.go('/rooms/settings/addaccount');
break;
case SettingsAction.newStory:
context.go('/rooms/stories/create');
break;
case SettingsAction.newGroup:
context.go('/rooms/newgroup');
break;
case SettingsAction.newSpace:
context.go('/rooms/newspace');
break;
// #Pangea
// case SettingsAction.addAccount:
// final consent = await showOkCancelAlertDialog(
// context: context,
// title: L10n.of(context)!.addAccount,
// message: L10n.of(context)!.enableMultiAccounts,
// okLabel: L10n.of(context)!.next,
// cancelLabel: L10n.of(context)!.cancel,
// );
// if (consent != OkCancelResult.ok) return;
// context.go('/rooms/settings/addaccount');
// break;
// case SettingsAction.newStory:
// context.go('/rooms/stories/create');
// break;
// case SettingsAction.newGroup:
// context.go('/rooms/newgroup');
// break;
// case SettingsAction.newSpace:
// context.go('/rooms/newspace');
// break;
// Pangea#
case SettingsAction.invite:
FluffyShare.shareInviteLink(context);
break;
@ -281,6 +395,39 @@ class ClientChooserButton extends StatelessWidget {
case SettingsAction.archive:
context.go('/rooms/archive');
break;
// #Pangea
case SettingsAction.newClass:
context.go('/rooms/newspace');
break;
case SettingsAction.newExchange:
context.go('/rooms/newspace/exchange');
break;
case SettingsAction.joinWithClassCode:
ClassCodeUtil.joinWithClassCodeDialog(
context,
controller.pangeaController,
null,
);
break;
case SettingsAction.findAConversationPartner:
findConversationPartnerDialog(
context,
controller.pangeaController,
);
break;
case SettingsAction.classAnalytics:
context.go('/rooms/analytics');
break;
case SettingsAction.myAnalytics:
context.go('/rooms/mylearning');
break;
case SettingsAction.findAClass:
debugger(when: kDebugMode, message: "left to implement");
break;
case SettingsAction.logout:
pLogoutAction(context);
break;
// Pangea#
}
}
}
@ -356,11 +503,23 @@ class ClientChooserButton extends StatelessWidget {
}
enum SettingsAction {
addAccount,
newStory,
newGroup,
newSpace,
// #Pangea
// addAccount,
// newStory,
// newGroup,
// newSpace,
// Pangea#
invite,
settings,
archive,
// #Pangea
joinWithClassCode,
classAnalytics,
myAnalytics,
findAClass,
findAConversationPartner,
logout,
newClass,
newExchange
// Pangea#
}

View file

@ -1,6 +1,6 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import '../../config/themes.dart';
class NaviRailItem extends StatefulWidget {
@ -16,8 +16,8 @@ class NaviRailItem extends StatefulWidget {
required this.onTap,
required this.icon,
this.selectedIcon,
Key? key,
}) : super(key: key);
super.key,
});
@override
State<NaviRailItem> createState() => _NaviRailItemState();

View file

@ -1,17 +1,22 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/archive_space.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import '../../utils/localized_exception_extension.dart';
import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
@ -21,9 +26,9 @@ class SpaceView extends StatefulWidget {
final ScrollController scrollController;
const SpaceView(
this.controller, {
Key? key,
super.key,
required this.scrollController,
}) : super(key: key);
});
@override
State<SpaceView> createState() => _SpaceViewState();
@ -35,9 +40,15 @@ class _SpaceViewState extends State<SpaceView> {
String? prevBatch;
void _refresh() {
setState(() {
_requests.remove(widget.controller.activeSpaceId);
});
// #Pangea
if (mounted) {
// Pangea#
setState(() {
_requests.remove(widget.controller.activeSpaceId);
});
// #Pangea
}
// Pangea#
}
Future<GetSpaceHierarchyResponse> getFuture(String activeSpaceId) =>
@ -73,7 +84,10 @@ class _SpaceViewState extends State<SpaceView> {
}
if (spaceChild.roomType == 'm.space') {
if (spaceChild.roomId == widget.controller.activeSpaceId) {
context.go('/rooms/${spaceChild.roomId}');
// #Pangea
// context.go('/rooms/${spaceChild.roomId}');
context.push('/spaces/${spaceChild.roomId}');
// Pangea#
} else {
widget.controller.setActiveSpace(spaceChild.roomId);
}
@ -110,11 +124,31 @@ class _SpaceViewState extends State<SpaceView> {
label: L10n.of(context)!.removeFromSpace,
icon: Icons.delete_sweep_outlined,
),
// #Pangea
if (room != null &&
room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin)
SheetAction(
key: SpaceChildContextAction.addToSpace,
label: L10n.of(context)!.addToSpace,
icon: Icons.workspaces_outlined,
),
if (room != null && room.isRoomAdmin)
SheetAction(
key: SpaceChildContextAction.archive,
label: room.isSpace
? L10n.of(context)!.archiveSpace
: L10n.of(context)!.archive,
icon: Icons.architecture_outlined,
),
// Pangea#
if (room != null)
SheetAction(
key: SpaceChildContextAction.leave,
label: L10n.of(context)!.leave,
icon: Icons.delete_outlined,
// #Pangea
// icon: Icons.delete_outlined,
icon: Icons.arrow_forward,
// Pangea#
isDestructiveAction: true,
),
],
@ -137,9 +171,49 @@ class _SpaceViewState extends State<SpaceView> {
future: () => activeSpace!.removeSpaceChild(spaceChild!.roomId),
);
break;
// #Pangea
case SpaceChildContextAction.archive:
widget.controller.cancelAction();
widget.controller.toggleSelection(room!.id);
room.isSpace
? await showFutureLoadingDialog(
context: context,
future: () async {
await archiveSpace(
room,
Matrix.of(context).client,
);
widget.controller.selectedRoomIds.clear();
},
)
: await widget.controller.archiveAction();
_refresh();
break;
case SpaceChildContextAction.addToSpace:
widget.controller.cancelAction();
widget.controller.toggleSelection(room!.id);
await widget.controller.addToSpace();
break;
// Pangea#
}
}
// #Pangea
StreamSubscription<SyncUpdate>? _roomSubscription;
@override
void initState() {
super.initState();
_refresh();
}
@override
void dispose() {
super.dispose();
_roomSubscription?.cancel();
}
// Pangea#
@override
Widget build(BuildContext context) {
final client = Matrix.of(context).client;
@ -147,12 +221,14 @@ class _SpaceViewState extends State<SpaceView> {
final allSpaces = client.rooms.where((room) => room.isSpace);
if (activeSpaceId == null) {
final rootSpaces = allSpaces
.where(
(space) => !allSpaces.any(
(parentSpace) => parentSpace.spaceChildren
.any((child) => child.roomId == space.id),
),
)
// #Pangea
// .where(
// (space) => !allSpaces.any(
// (parentSpace) => parentSpace.spaceChildren
// .any((child) => child.roomId == space.id),
// ),
// )
// Pangea#
.toList();
return CustomScrollView(
@ -172,17 +248,34 @@ class _SpaceViewState extends State<SpaceView> {
leading: Avatar(
mxContent: rootSpace.avatar,
name: displayname,
// #Pangea
littleIcon: rootSpace.roomTypeIcon,
// Pangea#
),
title: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// #Pangea
subtitle: Text(
L10n.of(context)!
.numChats(rootSpace.spaceChildren.length.toString()),
rootSpace.membership == Membership.join
? L10n.of(context)!.numChats(
rootSpace.spaceChildren.length.toString(),
)
: L10n.of(context)!.youreInvited,
),
onTap: () => widget.controller.setActiveSpace(rootSpace.id),
onTap: () => chatListHandleSpaceTap(
context,
widget.controller,
rootSpaces[i],
),
// subtitle: Text(
// L10n.of(context)!
// .numChats(rootSpace.spaceChildren.length.toString()),
// ),
// onTap: () => widget.controller.setActiveSpace(rootSpace.id),
// Pangea#
onLongPress: () =>
_onSpaceChildContextMenu(null, rootSpace),
trailing: const Icon(Icons.chevron_right_outlined),
@ -195,6 +288,25 @@ class _SpaceViewState extends State<SpaceView> {
],
);
}
// #Pangea
_roomSubscription = client.onSync.stream
.where((event) => event.rooms?.join?.isNotEmpty ?? false)
.listen((event) {
if (mounted) {
final List<String> joinedRoomIds = event.rooms!.join!.keys.toList();
final joinedRoomFutures = joinedRoomIds.map(
(joinedRoomId) => client.waitForRoomInSync(
joinedRoomId,
join: true,
),
);
Future.wait(joinedRoomFutures).then((_) {
_refresh();
});
}
});
// Pangea#
return FutureBuilder<GetSpaceHierarchyResponse>(
future: getFuture(activeSpaceId),
builder: (context, snapshot) {
@ -232,7 +344,48 @@ class _SpaceViewState extends State<SpaceView> {
(space) =>
space.spaceChildren.any((child) => child.roomId == activeSpaceId),
);
final spaceChildren = response.rooms;
// #Pangea
// final spaceChildren = response.rooms;
List<SpaceRoomsChunk> spaceChildren = response.rooms;
final space = Matrix.of(context).client.getRoomById(activeSpaceId);
if (space != null) {
final matchingSpaceChildren = space.spaceChildren
.where(
(spaceChild) => spaceChildren
.map((hierarchyMember) => hierarchyMember.roomId)
.contains(spaceChild.roomId),
)
.toList();
spaceChildren = spaceChildren
.where(
(spaceChild) =>
matchingSpaceChildren.any(
(matchingSpaceChild) =>
matchingSpaceChild.roomId == spaceChild.roomId &&
matchingSpaceChild.suggested == true,
) ||
[Membership.join, Membership.invite].contains(
Matrix.of(context)
.client
.getRoomById(spaceChild.roomId)
?.membership,
),
)
.toList();
}
spaceChildren.sort((a, b) {
final bool aIsSpace = a.roomType == 'm.space';
final bool bIsSpace = b.roomType == 'm.space';
if (aIsSpace && !bIsSpace) {
return -1;
} else if (!aIsSpace && bIsSpace) {
return 1;
}
return 0;
});
// Pangea#
final canLoadMore = response.nextBatch != null;
return WillPopScope(
onWillPop: () async {
@ -318,15 +471,24 @@ class _SpaceViewState extends State<SpaceView> {
.withAlpha(128),
trailing: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Icon(Icons.edit_outlined),
// #Pangea
// child: Icon(Icons.edit_outlined),
child: Icon(Icons.settings_outlined),
// Pangea#
),
onTap: () => _onJoinSpaceChild(spaceChild),
// #Pangea
// onTap: () => _onJoinSpaceChild(spaceChild),
onTap: () => context.go('/spaces/${spaceChild.roomId}'),
// Pangea#
);
}
return ListTile(
leading: Avatar(
mxContent: spaceChild.avatarUrl,
name: spaceChild.name,
//#Pangea
littleIcon: room?.roomTypeIcon,
//Pangea#
),
title: Row(
children: [
@ -353,7 +515,16 @@ class _SpaceViewState extends State<SpaceView> {
],
],
),
onTap: () => _onJoinSpaceChild(spaceChild),
//#Pangea
// onTap: () => _onJoinSpaceChild(spaceChild),
onTap: room?.isSpace ?? false
? () => chatListHandleSpaceTap(
context,
widget.controller,
room!,
)
: () => _onJoinSpaceChild(spaceChild),
//Pangea#
onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),
subtitle: Text(
@ -386,4 +557,9 @@ enum SpaceChildContextAction {
join,
leave,
removeFromSpace,
// #Pangea
// deleteChat,
archive,
addToSpace
// Pangea#
}

Some files were not shown because too many files have changed in this diff Show more