code files
16
.env
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
17
.metadata
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
BIN
assets/colors.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/encryption.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
4
assets/pangea/apple.svg
Normal 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 |
BIN
assets/pangea/bot_faces/addled.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
assets/pangea/bot_faces/down.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/pangea/bot_faces/left.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
assets/pangea/bot_faces/right.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/pangea/bot_faces/shocked.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
assets/pangea/bot_faces/surprised.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
6
assets/pangea/google.svg
Normal 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
|
After Width: | Height: | Size: 13 KiB |
5
assets/pangea/pangea_logo.svg
Normal 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/sounds/WoodenBeaver_stereo_message-new-instant.ogg
Normal file
BIN
assets/start_chat.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
9
docs/en/privacy.html
Normal 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
|
|
@ -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.
|
||||
|
|
@ -116,4 +116,4 @@
|
|||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
1
docs/tailwind.css
Normal file
1
fastlane
|
|
@ -1 +0,0 @@
|
|||
./android/fastlane
|
||||
BIN
fonts/Inconsolata/Inconsolata-Black.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-Bold.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-ExtraBold.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-ExtraLight.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-Light.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-Medium.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-Regular.ttf
Normal file
BIN
fonts/Inconsolata/Inconsolata-SemiBold.ttf
Normal 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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
|
|||
91
lib/config/firebase_options.dart
Normal 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',
|
||||
);
|
||||
}
|
||||
|
|
@ -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']!)!,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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!,
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
50
lib/pages/chat/cupertino_widgets_bottom_sheet.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
49
lib/pages/chat/widgets_bottom_sheet.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
}
|
||||
|
|
|
|||