1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2025-07-01 14:13:29 +00:00

84 Commits
1.1.8 ... 1.2.1

Author SHA1 Message Date
c5ee235ebd Users can report their own generated content (thanks Google Play reviewers) 2022-08-04 13:45:10 +02:00
a24bb147c8 Update version plugin 2022-08-04 12:23:29 +02:00
ae56635564 Update flutter_webrtc plugin 2022-08-04 12:20:36 +02:00
c02f6f1deb Update firebase_messaging plugin 2022-08-04 12:14:52 +02:00
31fed62616 Update file_picker plugin 2022-08-04 12:07:03 +02:00
aff7e7434c flutter pub upgrade 2022-08-04 11:31:37 +02:00
f18b632ac8 Start to work on new version 2022-06-11 16:19:57 +02:00
b14eae6689 Update Gradle and target Android version 2022-06-11 15:15:38 +02:00
bae83430ab Update application dependencies 2022-06-11 15:04:11 +02:00
2c44793def Updated Flutter to v3 2022-06-11 14:12:44 +02:00
b86a456a03 Start to work on new version 2022-03-18 21:02:14 +01:00
e969c85188 Add missing french translations 2022-03-18 20:37:19 +01:00
de8b4f7fb4 Fix incorrect build number 2022-03-18 20:33:15 +01:00
f624971717 Remove report group icon on groups list if user is already a member of the group 2022-03-18 20:16:27 +01:00
49a1098a28 Can report group 2022-03-18 20:13:45 +01:00
64bbce2084 Can report user 2022-03-18 19:29:26 +01:00
504be2e5ef Close report dialog if user has already sent a report 2022-03-18 19:23:35 +01:00
79ed8e934e Can report conversation message 2022-03-18 19:21:08 +01:00
1bd7840be6 Can report conversation 2022-03-18 19:11:06 +01:00
e80232931e Can report comment 2022-03-18 19:05:21 +01:00
20b19d0a4a Can report post 2022-03-18 18:45:58 +01:00
ec16984b8a Managed to send reports 2022-03-18 18:29:25 +01:00
80a1c4e0c4 Start to build report dialog 2022-03-18 17:54:19 +01:00
512b058d34 Fix potential load issue on user page 2022-03-12 15:22:14 +01:00
73f20a543d Start to work on version 1.1.12 2022-03-12 11:15:26 +01:00
2ee4590364 Improve management of cache key 2022-03-12 10:57:11 +01:00
2a50190e94 Add cache key for conversation messages 2022-03-12 10:31:13 +01:00
5040fbb101 Fix refresh issue 2022-03-12 10:14:32 +01:00
11829273e2 Revert "Fix refresh issue"
This reverts commit 56c5eb33
2022-03-12 10:13:44 +01:00
56c5eb335b Fix refresh issue 2022-03-12 10:13:32 +01:00
0bd7426813 Attempt to keep post create form 2022-03-12 09:58:23 +01:00
75c596226b Show a message if user has no conversation yet 2022-03-12 09:47:58 +01:00
a0abdc4f1b Show a message if user has no friend yet 2022-03-12 09:44:21 +01:00
2ac5caaf96 Fix a bug on friends list screen 2022-03-12 09:42:07 +01:00
98354ee3cc Update compile target SDK version 2022-03-11 17:29:32 +01:00
9e6d3761fe Finish to fix notices 2022-03-11 17:16:46 +01:00
48c9ee37b6 Fix a few notices 2022-03-11 17:13:54 +01:00
41446f0e5b Fix a few notices 2022-03-11 17:09:37 +01:00
0f9a59b4a7 Fix a few notices 2022-03-11 17:02:06 +01:00
f0f7096c94 Fix a few notices 2022-03-11 16:41:29 +01:00
adbc036c16 Fix a few notices 2022-03-11 16:40:56 +01:00
45e8f34c81 Fix a few notices 2022-03-11 16:36:42 +01:00
06312512a6 Fix a few notices 2022-03-11 16:27:01 +01:00
5398970868 Fix all warnings 2022-03-11 16:21:35 +01:00
820491b09a Continue to fix issues 2022-03-10 20:39:06 +01:00
7a0b44e446 Continue to fix issues 2022-03-10 20:36:55 +01:00
299a95ea45 Continue to fix issues 2022-03-10 20:28:07 +01:00
3a997cdc56 Start to fix null safety migration errors 2022-03-10 19:39:57 +01:00
ab2c5da0da Progressing with null safety migration 2022-03-10 19:02:06 +01:00
2424fd38d6 Prepare null safety migration 2022-03-10 18:47:16 +01:00
3c08384a4f Migrate from package_info to package_info_plus 2022-03-10 18:42:48 +01:00
c199540aff Migrate from connectivity to connectivity_plus 2022-03-10 18:37:59 +01:00
49a9186978 Update emoji package 2022-03-10 18:31:29 +01:00
1f3230363e Update permission_handler dependency 2022-03-10 18:21:37 +01:00
1a4988d192 Update chewie & wakelock dependencies 2022-03-10 18:14:59 +01:00
cd7cafe315 Run pub update 2022-03-10 18:07:15 +01:00
42b4f99efa Update Flutter to v2.10 2022-03-10 17:28:21 +01:00
173487f801 Start to work on version 1.1.11 2021-12-30 14:34:19 +01:00
61e279f719 Show banner in tablet mode 2021-12-30 13:45:22 +01:00
94ae3434e4 Adapt banner to large screens 2021-12-30 13:36:12 +01:00
78ae2a574a Show banner on Forez main route 2021-12-30 12:32:50 +01:00
75b06e7df8 Show banner on welcome route 2021-12-30 12:28:06 +01:00
71a72d4f04 Add support for banner timeout 2021-12-30 12:22:10 +01:00
90b3ffbe81 Fix lang selection 2021-12-30 12:11:26 +01:00
bfe932c053 Start to display banner 2021-12-30 12:02:18 +01:00
4fd8c4d613 Fix deprecated methods call 2021-12-28 19:09:00 +01:00
ecc4f5bffe Update some Gradle dependencies 2021-12-28 19:03:37 +01:00
c849ee0bac Update Gradle & Target sdk 32 2021-12-28 18:35:32 +01:00
04114dede1 Update Gradle version to 6.2 2021-12-28 17:49:33 +01:00
fff33f907a Update firebase_messaging dependency 2021-12-28 17:29:05 +01:00
9a82301c52 Update web_socket_channel dependency 2021-12-28 17:25:49 +01:00
48ececf93c Update video_thumbnail dependency 2021-12-28 17:23:26 +01:00
bc6068c2a1 Update record_mp3 dependency 2021-12-28 17:18:37 +01:00
5b680bb922 Fix issue on posts list not refreshing when a new post is created 2021-12-28 17:15:25 +01:00
2b05fbda35 Update image_picker dependency 2021-12-28 17:06:21 +01:00
30494ff74a Update flutter_webrtc dependency 2021-12-28 16:59:32 +01:00
062abc5a03 Update file_picker dependency 2021-12-28 16:46:46 +01:00
21506f769e Updated cached_network_image package 2021-12-28 16:43:15 +01:00
e180f0bc13 Replace identicon with jdenticon 2021-12-28 16:34:14 +01:00
7ae50e21a4 Fix all code warnings 2021-12-28 15:33:27 +01:00
ae75429b1d Update to Flutter 2.8.1 2021-12-28 15:23:08 +01:00
f2380ba60a Start to work on version 1.1.10 2021-10-28 16:30:22 +02:00
b4140e61ad Fix little bug 2021-10-28 16:20:31 +02:00
aa44688d66 Start to work on v1.1.9 2021-07-13 18:42:21 +02:00
276 changed files with 4527 additions and 4073 deletions

2
.gitignore vendored
View File

@ -74,3 +74,5 @@ lib/*private*.dart
.flutter-plugins-dependencies .flutter-plugins-dependencies
local.properties
.gradle

View File

@ -22,6 +22,7 @@ if (flutterVersionName == null) {
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
@ -33,24 +34,33 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 30 compileSdkVersion flutter.compileSdkVersion
compileOptions { compileOptions {
// Required to use WebRTC
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/java'
}
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
// TODO remove this fix to use Gradle plugin 4 ASAP
checkReleaseBuilds false
} }
defaultConfig { defaultConfig {
applicationId "org.communiquons.comunic" applicationId "org.communiquons.comunic"
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -76,9 +86,7 @@ android {
buildTypes { buildTypes {
release { release {
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
@ -109,10 +117,11 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.neovisionaries:nv-websocket-client:2.14' implementation 'com.neovisionaries:nv-websocket-client:2.14'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -59,7 +59,8 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
android:exported="true">
<!-- This keeps the window background of the activity showing <!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen there is no splash screen (such as the default splash screen
@ -69,12 +70,6 @@
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" /> android:resource="@style/NormalTheme" />
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -100,7 +100,10 @@ public class NotificationsService extends Service implements Runnable {
Intent notificationIntent = new Intent(this, MainActivity.class); Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0); 0,
notificationIntent,
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0);
Notification notification = new NotificationCompat.Builder(this, SVC_CHANNEL_ID) Notification notification = new NotificationCompat.Builder(this, SVC_CHANNEL_ID)
.setContentTitle("Comunic") .setContentTitle("Comunic")
.setContentText(getText(R.string.independent_push_notification_notification_text)) .setContentText(getText(R.string.independent_push_notification_notification_text))
@ -197,12 +200,12 @@ public class NotificationsService extends Service implements Runnable {
ws.setPingInterval(PING_INTERVAL); ws.setPingInterval(PING_INTERVAL);
ws.addListener(new WebSocketAdapter() { ws.addListener(new WebSocketAdapter() {
@Override @Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception { public void onConnected(WebSocket websocket, Map<String, List<String>> headers) {
Log.v(TAG, "Connected to independent push notifications service!"); Log.v(TAG, "Connected to independent push notifications service!");
} }
@Override @Override
public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) {
Log.v(TAG, "Disconnected from independent push notifications websocket!"); Log.v(TAG, "Disconnected from independent push notifications websocket!");
synchronized (lock) { synchronized (lock) {
lock.notify(); lock.notify();
@ -210,12 +213,12 @@ public class NotificationsService extends Service implements Runnable {
} }
@Override @Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { public void onTextFrame(WebSocket websocket, WebSocketFrame frame) {
handleTextFrame(frame); handleTextFrame(frame);
} }
@Override @Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception { public void onError(WebSocket websocket, WebSocketException cause) {
Log.e(TAG, "An error occured, closing WebSocket!"); Log.e(TAG, "An error occured, closing WebSocket!");
cause.printStackTrace(); cause.printStackTrace();
websocket.disconnect(); websocket.disconnect();
@ -254,17 +257,14 @@ public class NotificationsService extends Service implements Runnable {
} }
} }
private void dropNotification(String id) throws Exception { private void dropNotification(String id) {
new Handler(Looper.getMainLooper()).post(new Runnable() { new Handler(Looper.getMainLooper()).post(() -> {
@Override
public void run() {
NotificationManagerCompat notifManager = NotificationManagerCompat.from(NotificationsService.this); NotificationManagerCompat notifManager = NotificationManagerCompat.from(NotificationsService.this);
notifManager.cancel(id, NOTIFS_ID); notifManager.cancel(id, NOTIFS_ID);
}
}); });
} }
private void sendNotification(PushNotification n) throws Exception { private void sendNotification(PushNotification n) {
new Handler(Looper.getMainLooper()).post(() -> postNotification(n)); new Handler(Looper.getMainLooper()).post(() -> postNotification(n));
} }
@ -273,7 +273,12 @@ public class NotificationsService extends Service implements Runnable {
createPushNotificationChannel(); createPushNotificationChannel();
Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFS_CHANNEL_ID) NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notifications) .setSmallIcon(R.drawable.ic_notifications)

View File

@ -1,6 +0,0 @@
package org.communiquons.comunic
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,21 +1,23 @@
buildscript { buildscript {
ext.kotlin_version = '1.5.30'
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.4' classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// Firebase // Firebase
classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.gms:google-services:4.3.10'
} }
} }
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -340,6 +340,7 @@
"Failed to remove conversation logo!": "Erreur lors de la suppression du logo de la conversation !", "Failed to remove conversation logo!": "Erreur lors de la suppression du logo de la conversation !",
"Failed to remove member!": "Echec de la suppression d'un membre !", "Failed to remove member!": "Echec de la suppression d'un membre !",
"Failed to send a file!": "Erreur lors de l'envoi d'un fichier !", "Failed to send a file!": "Erreur lors de l'envoi d'un fichier !",
"Failed to send report!": "Erreur lors de l'envoi du signalement !",
"Failed to start recording!": "Erreur lors du lancement de l'enregistrement !", "Failed to start recording!": "Erreur lors du lancement de l'enregistrement !",
"Failed to toggle admin status of user!": "Echec du changement du status administrateur d'un membre !", "Failed to toggle admin status of user!": "Echec du changement du status administrateur d'un membre !",
"Failed to update conversation settings!": "Echec de la mise à jour des paramètres de la conversation !", "Failed to update conversation settings!": "Echec de la mise à jour des paramètres de la conversation !",
@ -500,6 +501,7 @@
"Please check back soon!": "Revenez plus tard finir la configuration de l'application !", "Please check back soon!": "Revenez plus tard finir la configuration de l'application !",
"Please choose new account image visibility level:": "Veuillez choisir un nouveau niveau de visibilité pour votre image de compte :", "Please choose new account image visibility level:": "Veuillez choisir un nouveau niveau de visibilité pour votre image de compte :",
"Please choose now the Forez group you want to join...": "Veuillez maintenant choisir le groupe #Forez que vous souhaitez rejoindre...", "Please choose now the Forez group you want to join...": "Veuillez maintenant choisir le groupe #Forez que vous souhaitez rejoindre...",
"Please choose the reason of your report:": "Veuillez indiquer la raison de votre signalement",
"Please click on the day you will be in the plain, so that everyone gets informed ! ;)": "Veuillez cliquer sur les jours où vous serez présent dans la plaine du Forez, que tout le monde soit au courant ;)", "Please click on the day you will be in the plain, so that everyone gets informed ! ;)": "Veuillez cliquer sur les jours où vous serez présent dans la plaine du Forez, que tout le monde soit au courant ;)",
"Please enter message content: ": "Veuillez entrer le contenu du message :", "Please enter message content: ": "Veuillez entrer le contenu du message :",
"Please enter new message content:": "Veuillez entrer le contenu du nouveau message :", "Please enter new message content:": "Veuillez entrer le contenu du nouveau message :",
@ -534,6 +536,7 @@
"Question 1": "Question 1", "Question 1": "Question 1",
"Question 2": "Question 2", "Question 2": "Question 2",
"Ready": "Prêt", "Ready": "Prêt",
"Reason of report": "Raison du signalement",
"Receive notifications for the conversations you follow.": "Recevoir des notifications push pour les conversations que vous suivez", "Receive notifications for the conversations you follow.": "Recevoir des notifications push pour les conversations que vous suivez",
"Record audio": "Faire un enregistrement audio", "Record audio": "Faire un enregistrement audio",
"Recording...": "Enregistrement...", "Recording...": "Enregistrement...",
@ -543,6 +546,8 @@
"Remove": "Supprimer", "Remove": "Supprimer",
"Remove selected image": "Supprimer l'image sélectionnée", "Remove selected image": "Supprimer l'image sélectionnée",
"Replace image": "Remplacer l'image", "Replace image": "Remplacer l'image",
"Report abuse": "Signaler un abus",
"Report successfully saved. Thank you for your contribution!": "Le report a bien été pris en compte. Merci pour votre contribution !",
"Request membership": "Demander de rejoindre le groupe", "Request membership": "Demander de rejoindre le groupe",
"Requested": "En attente", "Requested": "En attente",
"Respond to survey": "Répondre au sondage", "Respond to survey": "Répondre au sondage",
@ -642,8 +647,11 @@
"You can search the people you know and ask them to become your friends!": "Vous pouvez rechercher vos connaissances et les demander en amis !", "You can search the people you know and ask them to become your friends!": "Vous pouvez rechercher vos connaissances et les demander en amis !",
"You can search the people you know and ask them to become your friends!\\n\\nThis will help you to reach them to exchange information!": "Vous pouvez rechercher vos connaissances et les demander en amis, pour entrer facilement en contact avec elles !", "You can search the people you know and ask them to become your friends!\\n\\nThis will help you to reach them to exchange information!": "Vous pouvez rechercher vos connaissances et les demander en amis, pour entrer facilement en contact avec elles !",
"You can use this virtual directory.": "Vous pouvez utiliser ce répertoire virtuel.", "You can use this virtual directory.": "Vous pouvez utiliser ce répertoire virtuel.",
"You do not have any conversation yet!": "Vous n'avez pas encore de conversation !",
"You do not have any friend yet!": "Vous n'avez pas encore d'amis sur Comunic !",
"You do not have any notification now.": "Vous n'avez pas de notification pour l'intant.", "You do not have any notification now.": "Vous n'avez pas de notification pour l'intant.",
"You do not have any unread conversation yet...": "Vous n'avez aucune conversation non lue pour le moment...", "You do not have any unread conversation yet...": "Vous n'avez aucune conversation non lue pour le moment...",
"You have already sent a report for this resource!": "Vous avez déjà soumis un signalement pour cette ressource !",
"You must accept the Terms Of Service to continue.": "Vous devez accepter les Conditions d'utilisation pour continuer.", "You must accept the Terms Of Service to continue.": "Vous devez accepter les Conditions d'utilisation pour continuer.",
"You security questions have been successfully updated!": "Vos questions de sécurité ont été mises avec succès !", "You security questions have been successfully updated!": "Vos questions de sécurité ont été mises avec succès !",
"You will need to restart the application to apply changes": "Vous aurez besoin de redémarrer l'application pour appliquer les changements", "You will need to restart the application to apply changes": "Vous aurez besoin de redémarrer l'application pour appliquer les changements",

View File

@ -5,7 +5,7 @@ make stable_release_split_per_abi && \
mv build/app/outputs/flutter-apk/app-armeabi-v7a-stable-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-armeabi-v7a-stable-release.apk "$DEST" && \
mv build/app/outputs/flutter-apk/app-arm64-v8a-stable-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-arm64-v8a-stable-release.apk "$DEST" && \
mv build/app/outputs/flutter-apk/app-x86_64-stable-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-x86_64-stable-release.apk "$DEST" && \
mv build/app/outputs/mapping/stable/release/mapping.txt "$DEST" && \ mv build/app/outputs/mapping/stableRelease/mapping.txt "$DEST" && \
make stable_release && \ make stable_release && \
mv build/app/outputs/flutter-apk/app-stable-release.apk "$DEST" mv build/app/outputs/flutter-apk/app-stable-release.apk "$DEST"

View File

@ -5,4 +5,4 @@ make forez_release_split_per_abi && \
mv build/app/outputs/flutter-apk/app-armeabi-v7a-forez-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-armeabi-v7a-forez-release.apk "$DEST" && \
mv build/app/outputs/flutter-apk/app-arm64-v8a-forez-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-arm64-v8a-forez-release.apk "$DEST" && \
mv build/app/outputs/flutter-apk/app-x86_64-forez-release.apk "$DEST" && \ mv build/app/outputs/flutter-apk/app-x86_64-forez-release.apk "$DEST" && \
mv build/app/outputs/mapping/forez/release/mapping.txt "$DEST" mv build/app/outputs/mapping/forezRelease/mapping.txt "$DEST"

View File

@ -0,0 +1,28 @@
/// What kind of content that can be reported
enum ReportTargetType {
Post,
Comment,
Conversation,
ConversationMessage,
User,
Group
}
extension ReportTargetExt on ReportTargetType {
String get apiId {
switch (this) {
case ReportTargetType.Post:
return "post";
case ReportTargetType.Comment:
return "comment";
case ReportTargetType.Conversation:
return "conversation";
case ReportTargetType.ConversationMessage:
return "conversation_message";
case ReportTargetType.User:
return "user";
case ReportTargetType.Group:
return "group";
}
}
}

View File

@ -8,7 +8,7 @@ import 'package:comunic/models/advanced_group_info.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
AdvancedGroupInfo _forezGroup; AdvancedGroupInfo? _forezGroup;
class ForezGroupHelper { class ForezGroupHelper {
static Future<void> setId(int groupID) async { static Future<void> setId(int groupID) async {
@ -16,7 +16,7 @@ class ForezGroupHelper {
.setInt(PreferencesKeyList.FOREZ_GROUP, groupID); .setInt(PreferencesKeyList.FOREZ_GROUP, groupID);
} }
static Future<int> getId() async { static Future<int?> getId() async {
return (await PreferencesHelper.getInstance()) return (await PreferencesHelper.getInstance())
.getInt(PreferencesKeyList.FOREZ_GROUP); .getInt(PreferencesKeyList.FOREZ_GROUP);
} }
@ -27,7 +27,7 @@ class ForezGroupHelper {
_forezGroup = res.info; _forezGroup = res.info;
} }
static AdvancedGroupInfo getGroup() => _forezGroup; static AdvancedGroupInfo? getGroup() => _forezGroup;
} }
AdvancedGroupInfo get forezGroup => ForezGroupHelper.getGroup(); AdvancedGroupInfo? get forezGroup => ForezGroupHelper.getGroup();

View File

@ -11,10 +11,10 @@ import 'package:flutter/material.dart';
class ForezConfig extends Config { class ForezConfig extends Config {
ForezConfig({ ForezConfig({
@required String apiServerName, required String apiServerName,
@required String apiServerUri, required String apiServerUri,
@required bool apiServerSecure, required bool apiServerSecure,
@required String clientName, required String clientName,
}) : super( }) : super(
apiServerName: apiServerName, apiServerName: apiServerName,
apiServerUri: apiServerUri, apiServerUri: apiServerUri,

View File

@ -11,7 +11,7 @@ import 'package:comunic/models/config.dart';
/// Fix HTTPS issue /// Fix HTTPS issue
class MyHttpOverride extends HttpOverrides { class MyHttpOverride extends HttpOverrides {
@override @override
HttpClient createHttpClient(SecurityContext context) { HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context) return super.createHttpClient(context)
..badCertificateCallback = (cert, host, port) { ..badCertificateCallback = (cert, host, port) {
return host == "devweb.local"; // Forcefully trust local website return host == "devweb.local"; // Forcefully trust local website

View File

@ -29,7 +29,7 @@ List<Widget> buildTour(TourRouteState state) {
msgTwo: tr("Let's first join a Forez group!"), msgTwo: tr("Let's first join a Forez group!"),
), ),
JoinForezGroupPane( JoinForezGroupPane(
key: state.pubKeys[_JOIN_GROUP_KEY_ID], key: state.pubKeys[_JOIN_GROUP_KEY_ID] as GlobalKey<JoinGroupPaneBodyState>?,
onUpdated: () => state.rebuild(), onUpdated: () => state.rebuild(),
), ),
FirstTourPane( FirstTourPane(
@ -51,7 +51,7 @@ List<Widget> buildTour(TourRouteState state) {
// Forez specific features // Forez specific features
PresentationPane( PresentationPane(
icon: Icons.calendar_today, icon: Icons.calendar_today,
title: tr("Presence in Forez"), title: tr("Presence in Forez")!,
text: tr( text: tr(
"Easily specify the days you are in Forez plain, so that everyone can know it!"), "Easily specify the days you are in Forez plain, so that everyone can know it!"),
actionTitle: tr("Do it now!"), actionTitle: tr("Do it now!"),
@ -62,7 +62,7 @@ List<Widget> buildTour(TourRouteState state) {
// Chat pane // Chat pane
PresentationPane( PresentationPane(
icon: Icons.question_answer, icon: Icons.question_answer,
title: tr("Conversations"), title: tr("Conversations")!,
text: tr( text: tr(
"#Forez now integrates the conversation system of Comunic, so you have access both to public and private conversations!"), "#Forez now integrates the conversation system of Comunic, so you have access both to public and private conversations!"),
), ),

View File

@ -16,17 +16,17 @@ import 'package:flutter/material.dart';
class JoinForezGroupPane extends PresentationPane { class JoinForezGroupPane extends PresentationPane {
JoinForezGroupPane({ JoinForezGroupPane({
@required Function() onUpdated, required Function() onUpdated,
@required GlobalKey<JoinGroupPaneBodyState> key, required GlobalKey<JoinGroupPaneBodyState>? key,
}) : super( }) : super(
icon: Icons.login, icon: Icons.login,
title: tr("Join a Forez group"), title: tr("Join a Forez group")!,
child: (c) => _JoinGroupPaneBody( child: (c) => _JoinGroupPaneBody(
key: key, key: key,
onUpdated: onUpdated, onUpdated: onUpdated,
), ),
canGoNext: key?.currentState?.canGoNext ?? false, canGoNext: key?.currentState?.canGoNext ?? false,
onTapNext: (c) => key.currentState.validateChoice(), onTapNext: (c) => key!.currentState!.validateChoice(),
); );
} }
@ -34,10 +34,9 @@ class _JoinGroupPaneBody extends StatefulWidget {
final Function() onUpdated; final Function() onUpdated;
const _JoinGroupPaneBody({ const _JoinGroupPaneBody({
Key key, Key? key,
@required this.onUpdated, required this.onUpdated,
}) : assert(onUpdated != null), }) : super(key: key);
super(key: key);
@override @override
JoinGroupPaneBodyState createState() => JoinGroupPaneBodyState(); JoinGroupPaneBodyState createState() => JoinGroupPaneBodyState();
@ -46,10 +45,10 @@ class _JoinGroupPaneBody extends StatefulWidget {
class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> { class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
final _key = GlobalKey<AsyncScreenWidgetState>(); final _key = GlobalKey<AsyncScreenWidgetState>();
List<Group> _groups; late List<Group> _groups;
int _currChoice; int? _currChoice;
bool get canGoNext => _currChoice != null && _currChoice > 0; bool get canGoNext => _currChoice != null && _currChoice! > 0;
Group get _currGroup => _groups.firstWhere((e) => e.id == _currChoice); Group get _currGroup => _groups.firstWhere((e) => e.id == _currChoice);
@ -65,17 +64,17 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
key: _key, key: _key,
onReload: _load, onReload: _load,
onBuild: onBuild, onBuild: onBuild,
errorMessage: tr("Failed to load the list of Forez groups!")); errorMessage: tr("Failed to load the list of Forez groups!")!);
Widget onBuild() => ConstrainedBox( Widget onBuild() => ConstrainedBox(
constraints: BoxConstraints(maxWidth: 300), constraints: BoxConstraints(maxWidth: 300),
child: Column( child: Column(
children: [ children: [
Text(tr("Please choose now the Forez group you want to join...")), Text(tr("Please choose now the Forez group you want to join...")!),
]..addAll(_groups.map((e) => RadioListTile( ]..addAll(_groups.map((e) => RadioListTile(
value: e.id, value: e.id,
groupValue: _currChoice, groupValue: _currChoice,
onChanged: (v) => setState(() => _currChoice = _currChoice), onChanged: (dynamic v) => setState(() => _currChoice = e.id),
title: Text(e.name), title: Text(e.name),
subtitle: Text(e.membershipText), subtitle: Text(e.membershipText),
))), ))),
@ -112,7 +111,7 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
await alert(context, await alert(context,
"${tr("You can not access this group yet, please wait for a member of the group to accept your request.")}\n${tr("Hopefully this will not be too long.")}\n${tr("Please check back soon!")}"); "${tr("You can not access this group yet, please wait for a member of the group to accept your request.")}\n${tr("Hopefully this will not be too long.")}\n${tr("Please check back soon!")}");
_key.currentState.refresh(); _key.currentState!.refresh();
return false; return false;
} }
@ -121,8 +120,8 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
return true; return true;
} catch (e, s) { } catch (e, s) {
logError(e, s); logError(e, s);
snack(context, tr("Failed to register to group!")); snack(context, tr("Failed to register to group!")!);
_key.currentState.refresh(); _key.currentState!.refresh();
return false; return false;
} }
} }

View File

@ -14,7 +14,7 @@ import 'package:comunic/utils/conversations_utils.dart';
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart'; import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart';
/// Show information about a Forez member /// Show information about a Forez member
/// ///
@ -24,10 +24,9 @@ class ForezMemberProfileRoute extends StatefulWidget {
final int userID; final int userID;
const ForezMemberProfileRoute({ const ForezMemberProfileRoute({
Key key, Key? key,
@required this.userID, required this.userID,
}) : assert(userID != null), }) : super(key: key);
super(key: key);
@override @override
_ForezMemberProfileRouteState createState() => _ForezMemberProfileRouteState createState() =>
@ -39,13 +38,14 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
final _key = GlobalKey<AsyncScreenWidgetState>(); final _key = GlobalKey<AsyncScreenWidgetState>();
AdvancedUserInfo _user; late AdvancedUserInfo _user;
PresenceSet _presence; late PresenceSet _presence;
Future<void> _load() async { Future<void> _load() async {
_user = await ForezGroupsHelper.getMemberInfo(forezGroup.id, widget.userID); _user =
await ForezGroupsHelper.getMemberInfo(forezGroup!.id, widget.userID);
_presence = _presence =
await ForezPresenceHelper.getForUser(forezGroup.id, widget.userID); await ForezPresenceHelper.getForUser(forezGroup!.id, widget.userID);
} }
@override @override
@ -56,25 +56,25 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
loadingWidget: _buildLoading(), loadingWidget: _buildLoading(),
errorWidget: _buildError(), errorWidget: _buildError(),
errorMessage: tr( errorMessage: tr(
"Failed to load user information, maybe it is not a Forez member yet?")); "Failed to load user information, maybe it is not a Forez member yet?")!);
Widget _buildLoading() => Scaffold( Widget _buildLoading() => Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(tr("Loading...")), title: Text(tr("Loading...")!),
), ),
body: buildCenteredProgressBar(), body: buildCenteredProgressBar(),
); );
Widget _buildError() => Scaffold( Widget _buildError() => Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(tr("Error")), title: Text(tr("Error")!),
), ),
body: buildErrorCard( body: buildErrorCard(
tr("Failed to load user information, maybe it is not a Forez member yet?"), tr("Failed to load user information, maybe it is not a Forez member yet?"),
actions: [ actions: [
MaterialButton( MaterialButton(
onPressed: () => _key.currentState.refresh(), onPressed: () => _key.currentState!.refresh(),
child: Text(tr("Try again")), child: Text(tr("Try again")!),
textColor: Colors.white, textColor: Colors.white,
) )
]), ]),
@ -97,9 +97,7 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
background: Stack( background: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: <Widget>[ children: <Widget>[
_user.accountImageURL == null CachedNetworkImage(
? Container()
: CachedNetworkImage(
fit: BoxFit.cover, fit: BoxFit.cover,
imageUrl: _user.accountImageURL, imageUrl: _user.accountImageURL,
height: _appBarHeight, height: _appBarHeight,
@ -135,7 +133,7 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
: ListTile( : ListTile(
leading: Icon(Icons.note), leading: Icon(Icons.note),
title: TextWidget(content: DisplayedString(_user.publicNote)), title: TextWidget(content: DisplayedString(_user.publicNote)),
subtitle: Text(tr("Note")), subtitle: Text(tr("Note")!),
), ),
// Email address // Email address
@ -143,9 +141,9 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
? Container() ? Container()
: ListTile( : ListTile(
leading: Icon(Icons.email), leading: Icon(Icons.email),
title: Text(_user.emailAddress), title: Text(_user.emailAddress!),
subtitle: Text(tr("Email address")), subtitle: Text(tr("Email address")!),
trailing: CopyIcon(_user.emailAddress), trailing: CopyIcon(_user.emailAddress!),
), ),
// Location // Location
@ -153,9 +151,9 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
? Container() ? Container()
: ListTile( : ListTile(
leading: Icon(Icons.location_on), leading: Icon(Icons.location_on),
title: Text(_user.location), title: Text(_user.location!),
subtitle: Text(tr("Location")), subtitle: Text(tr("Location")!),
trailing: CopyIcon(_user.location), trailing: CopyIcon(_user.location!),
), ),
// Website // Website
@ -164,20 +162,20 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
: ListTile( : ListTile(
leading: Icon(Icons.link), leading: Icon(Icons.link),
title: Text(_user.personalWebsite), title: Text(_user.personalWebsite),
subtitle: Text(tr("Website")), subtitle: Text(tr("Website")!),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.open_in_new), icon: Icon(Icons.open_in_new),
onPressed: () => launch(_user.personalWebsite), onPressed: () => launchUrlString(_user.personalWebsite),
), ),
), ),
Divider(), Divider(),
ListTile( ListTile(
leading: Icon(Icons.calendar_today), leading: Icon(Icons.calendar_today),
title: Text(tr("Presence in Forez")), title: Text(tr("Presence in Forez")!),
subtitle: Text(_presence.containsDate(DateTime.now()) subtitle: Text(_presence.containsDate(DateTime.now())
? tr("Present today") ? tr("Present today")!
: tr("Absent")), : tr("Absent")!),
), ),
PresenceCalendarWidget(presenceSet: _presence), PresenceCalendarWidget(presenceSet: _presence),
Divider(), Divider(),

View File

@ -9,6 +9,7 @@ import 'package:comunic/ui/routes/main_route/page_info.dart';
import 'package:comunic/ui/screens/conversations_list_screen.dart'; import 'package:comunic/ui/screens/conversations_list_screen.dart';
import 'package:comunic/ui/screens/group_sections/forez_presence_section.dart'; import 'package:comunic/ui/screens/group_sections/forez_presence_section.dart';
import 'package:comunic/ui/screens/group_sections/group_posts_section.dart'; import 'package:comunic/ui/screens/group_sections/group_posts_section.dart';
import 'package:comunic/ui/widgets/banner_widget.dart';
import 'package:comunic/ui/widgets/safe_state.dart'; import 'package:comunic/ui/widgets/safe_state.dart';
import 'package:comunic/ui/widgets/status_widget.dart'; import 'package:comunic/ui/widgets/status_widget.dart';
import 'package:comunic/ui/widgets/tab_transition_widget.dart'; import 'package:comunic/ui/widgets/tab_transition_widget.dart';
@ -20,7 +21,7 @@ import 'package:flutter/material.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class ForezRoute extends StatefulWidget implements MainRoute { class ForezRoute extends StatefulWidget implements MainRoute {
const ForezRoute({Key key}) : super(key: key); const ForezRoute({Key? key}) : super(key: key);
@override @override
State<StatefulWidget> createState() => _MainRouteState(); State<StatefulWidget> createState() => _MainRouteState();
@ -39,7 +40,7 @@ class _MainRouteState extends MainController {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (forezGroup == null) return Text(tr("Missing Forez group!")); if (forezGroup == null) return Text(tr("Missing Forez group!")!);
return StatusWidget( return StatusWidget(
child: (c) => SafeArea( child: (c) => SafeArea(
@ -66,11 +67,11 @@ class _MainRouteState extends MainController {
@override @override
void openConversation(Conversation conv, {fullScreen: false}) { void openConversation(Conversation conv, {fullScreen: false}) {
// Forcefully open conversations in a "normal" way (do not display groups) // Forcefully open conversations in a "normal" way (do not display groups)
openConversationById(conv.id, fullScreen: fullScreen); openConversationById(conv.id!, fullScreen: fullScreen);
} }
@override @override
void openGroup(int groupID, {int conversationID}) => _unsupportedFeature(); void openGroup(int groupID, {int? conversationID}) => _unsupportedFeature();
@override @override
void openUserPage(int userID) => pushPage(PageInfo( void openUserPage(int userID) => pushPage(PageInfo(
@ -91,7 +92,7 @@ class _MainRouteState extends MainController {
enum _PopupMenuItems { ACTION_SETTINGS, ACTION_SIGN_OUT } enum _PopupMenuItems { ACTION_SETTINGS, ACTION_SIGN_OUT }
class ForezRouteBody extends StatefulWidget { class ForezRouteBody extends StatefulWidget {
ForezRouteBody({Key key}) : super(key: key); ForezRouteBody({Key? key}) : super(key: key);
@override @override
_ForezRouteBodyState createState() => _ForezRouteBodyState(); _ForezRouteBodyState createState() => _ForezRouteBodyState();
@ -111,11 +112,16 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
length: _tabs.length, length: _tabs.length,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(forezGroup.name), title: Text(forezGroup!.name),
actions: <Widget>[_buildPopupMenuButton()], actions: <Widget>[_buildPopupMenuButton()],
bottom: TabBar(tabs: _tabs), bottom: TabBar(tabs: _tabs),
), ),
body: TabBarView(children: _tabsPages), body: Column(
children: [
BannerWidget(),
Expanded(child: TabBarView(children: _tabsPages)),
],
),
), ),
); );
} }
@ -123,11 +129,11 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
Widget _buildPopupMenuButton() => PopupMenuButton<_PopupMenuItems>( Widget _buildPopupMenuButton() => PopupMenuButton<_PopupMenuItems>(
itemBuilder: (c) => [ itemBuilder: (c) => [
PopupMenuItem( PopupMenuItem(
child: Text(tr("Settings")), child: Text(tr("Settings")!),
value: _PopupMenuItems.ACTION_SETTINGS, value: _PopupMenuItems.ACTION_SETTINGS,
), ),
PopupMenuItem( PopupMenuItem(
child: Text(tr("Sign out")), child: Text(tr("Sign out")!),
value: _PopupMenuItems.ACTION_SIGN_OUT, value: _PopupMenuItems.ACTION_SIGN_OUT,
), ),
], ],
@ -137,10 +143,10 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
void _onMenuSelection(_PopupMenuItems value) { void _onMenuSelection(_PopupMenuItems value) {
switch (value) { switch (value) {
case _PopupMenuItems.ACTION_SETTINGS: case _PopupMenuItems.ACTION_SETTINGS:
MainController.of(context).openSettings(); MainController.of(context)!.openSettings();
break; break;
case _PopupMenuItems.ACTION_SIGN_OUT: case _PopupMenuItems.ACTION_SIGN_OUT:
MainController.of(context).requestLogout(); MainController.of(context)!.requestLogout();
break; break;
} }
} }
@ -149,29 +155,29 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
// Posts tab // Posts tab
_Tab( _Tab(
icon: Icons.auto_stories, icon: Icons.auto_stories,
title: tr("Posts"), title: tr("Posts")!,
widget: () => GroupPostsSection(group: forezGroup), widget: () => GroupPostsSection(group: forezGroup!),
), ),
// Presence tab // Presence tab
_Tab( _Tab(
icon: Icons.calendar_today, icon: Icons.calendar_today,
title: tr("Presence"), title: tr("Presence")!,
widget: () => ForezPresenceSection(groupID: forezGroup.id), widget: () => ForezPresenceSection(groupID: forezGroup!.id),
), ),
// Conversations tab // Conversations tab
_Tab( _Tab(
icon: Icons.question_answer, icon: Icons.question_answer,
title: tr("Conversations"), title: tr("Conversations")!,
widget: () => ConversationsListScreen(), widget: () => ConversationsListScreen(),
isUnread: (c) => StatusWidgetState.of(c).unreadConversations > 0, isUnread: (c) => StatusWidgetState.of(c)!.unreadConversations! > 0,
), ),
// Directory tab // Directory tab
_Tab( _Tab(
icon: Icons.import_contacts, icon: Icons.import_contacts,
title: tr("Directory"), title: tr("Directory")!,
widget: () => ForezDirectoryScreen(), widget: () => ForezDirectoryScreen(),
), ),
]; ];
@ -197,14 +203,12 @@ class _Tab {
final IconData icon; final IconData icon;
final String title; final String title;
final Widget Function() widget; final Widget Function() widget;
final bool Function(BuildContext) isUnread; final bool Function(BuildContext)? isUnread;
const _Tab({ const _Tab({
@required this.icon, required this.icon,
@required this.title, required this.title,
@required this.widget, required this.widget,
this.isUnread, this.isUnread,
}) : assert(icon != null), });
assert(title != null),
assert(widget != null);
} }

View File

@ -7,18 +7,18 @@ import 'package:flutter/material.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
Future<User> searchUser(BuildContext context, UsersList users) async { Future<User?> searchUser(BuildContext context, UsersList users) async {
return await showSearch<User>( return await showSearch<User?>(
context: context, delegate: _SearchDelegate(users)); context: context, delegate: _SearchDelegate(users));
} }
class _SearchDelegate extends SearchDelegate<User> { class _SearchDelegate extends SearchDelegate<User?> {
final UsersList _usersList; final UsersList _usersList;
_SearchDelegate(this._usersList) : assert(_usersList != null); _SearchDelegate(this._usersList);
@override @override
List<Widget> buildActions(BuildContext context) => null; List<Widget>? buildActions(BuildContext context) => null;
@override @override
Widget buildLeading(BuildContext context) => IconButton( Widget buildLeading(BuildContext context) => IconButton(
@ -28,7 +28,7 @@ class _SearchDelegate extends SearchDelegate<User> {
@override @override
Widget buildSuggestions(BuildContext context) { Widget buildSuggestions(BuildContext context) {
final list = _usersList final List<User> list = _usersList
.where((element) => .where((element) =>
element.fullName.toLowerCase().contains(query.toLowerCase())) element.fullName.toLowerCase().contains(query.toLowerCase()))
.toList(); .toList();

View File

@ -35,11 +35,11 @@ class ForezDirectoryScreen extends StatefulWidget {
class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> { class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
final _key = GlobalKey<AsyncScreenWidgetState>(); final _key = GlobalKey<AsyncScreenWidgetState>();
UsersList _users; late UsersList _users;
GroupMembersList _members; late GroupMembersList _members;
Future<void> _load() async { Future<void> _load() async {
_members = await GroupsHelper.getMembersList(forezGroup.id); _members = await GroupsHelper.getMembersList(forezGroup!.id);
_users = await UsersHelper().getListWithThrow(_members.usersID); _users = await UsersHelper().getListWithThrow(_members.usersID);
} }
@ -49,7 +49,7 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
AsyncScreenWidget( AsyncScreenWidget(
onReload: _load, onReload: _load,
onBuild: onBuild, onBuild: onBuild,
errorMessage: tr("Failed to load members list!"), errorMessage: tr("Failed to load members list!")!,
key: _key, key: _key,
), ),
Positioned( Positioned(
@ -88,13 +88,13 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
"Do you really want to cancel the invitation sent to %u%?", "Do you really want to cancel the invitation sent to %u%?",
args: {"u": user.fullName}))) return; args: {"u": user.fullName}))) return;
await GroupsHelper.cancelInvitation(forezGroup.id, user.id); await GroupsHelper.cancelInvitation(forezGroup!.id, user.id);
_key.currentState.refresh(); _key.currentState!.refresh();
break; break;
case _PopupMenuActions.ACCEPT_REQUEST: case _PopupMenuActions.ACCEPT_REQUEST:
await GroupsHelper.respondRequest(forezGroup.id, user.id, true); await GroupsHelper.respondRequest(forezGroup!.id, user.id, true);
_key.currentState.refresh(); _key.currentState!.refresh();
break; break;
case _PopupMenuActions.REJECT_REQUEST: case _PopupMenuActions.REJECT_REQUEST:
@ -104,13 +104,13 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
"Do you really want to reject the request of %u% to join the Forez group?", "Do you really want to reject the request of %u% to join the Forez group?",
args: {"u": user.fullName}))) return; args: {"u": user.fullName}))) return;
await GroupsHelper.respondRequest(forezGroup.id, user.id, false); await GroupsHelper.respondRequest(forezGroup!.id, user.id, false);
_key.currentState.refresh(); _key.currentState!.refresh();
break; break;
} }
} catch (e, s) { } catch (e, s) {
logError(e, s); logError(e, s);
snack(context, tr("Error while processing action!")); snack(context, tr("Error while processing action!")!);
} }
} }
@ -125,7 +125,7 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
} }
void _openUserProfile(User user) => void _openUserProfile(User user) =>
MainController.of(context).openUserPage(user.id); MainController.of(context)!.openUserPage(user.id);
} }
class _ForezMemberTile extends StatelessWidget { class _ForezMemberTile extends StatelessWidget {
@ -135,11 +135,11 @@ class _ForezMemberTile extends StatelessWidget {
final Function(User) onTap; final Function(User) onTap;
const _ForezMemberTile({ const _ForezMemberTile({
Key key, Key? key,
@required this.user, required this.user,
@required this.member, required this.member,
@required this.selectedAction, required this.selectedAction,
@required this.onTap, required this.onTap,
}) : super(key: key); }) : super(key: key);
@override @override
@ -147,7 +147,7 @@ class _ForezMemberTile extends StatelessWidget {
leading: AccountImageWidget(user: user), leading: AccountImageWidget(user: user),
title: Text(user.fullName), title: Text(user.fullName),
subtitle: Text(member.membershipText), subtitle: Text(member.membershipText),
trailing: !member.isAtLeastMember && forezGroup.isAtLeastModerator trailing: !member.isAtLeastMember && forezGroup!.isAtLeastModerator
? (member.isInvited ? (member.isInvited
? _buildInvitedButton() ? _buildInvitedButton()
: _buildRequestedButton()) : _buildRequestedButton())
@ -155,7 +155,7 @@ class _ForezMemberTile extends StatelessWidget {
onTap: member.isAtLeastMember ? () => onTap(user) : null, onTap: member.isAtLeastMember ? () => onTap(user) : null,
); );
Widget _buildConversationButton() => user.id == userID() Widget? _buildConversationButton() => user.id == userID()
? null ? null
: IconButton( : IconButton(
icon: Icon(Icons.message), icon: Icon(Icons.message),
@ -201,18 +201,13 @@ class _MembershipButton extends StatelessWidget {
final IconData icon; final IconData icon;
const _MembershipButton({ const _MembershipButton({
Key key, Key? key,
@required this.user, required this.user,
@required this.action, required this.action,
@required this.onTap, required this.onTap,
@required this.color, required this.color,
@required this.icon, required this.icon,
}) : assert(user != null), }) : super(key: key);
assert(action != null),
assert(onTap != null),
assert(color != null),
assert(icon != null),
super(key: key);
@override @override
Widget build(BuildContext context) => ElevatedButton( Widget build(BuildContext context) => ElevatedButton(

View File

@ -2,26 +2,33 @@
// Generated file. Do not edit. // Generated file. Do not edit.
// //
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars // ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: depend_on_referenced_packages
import 'package:connectivity_for_web/connectivity_for_web.dart'; import 'package:connectivity_plus_web/connectivity_plus_web.dart';
import 'package:file_picker/src/file_picker_web.dart'; import 'package:file_picker/_internal/file_picker_web.dart';
import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_core_web/firebase_core_web.dart';
import 'package:firebase_messaging_web/firebase_messaging_web.dart'; import 'package:firebase_messaging_web/firebase_messaging_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:image_cropper_for_web/image_cropper_for_web.dart';
import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:image_picker_for_web/image_picker_for_web.dart';
import 'package:package_info_plus_web/package_info_plus_web.dart';
import 'package:shared_preferences_web/shared_preferences_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart';
import 'package:url_launcher_web/url_launcher_web.dart'; import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:video_player_web/video_player_web.dart'; import 'package:video_player_web/video_player_web.dart';
import 'package:wakelock_web/wakelock_web.dart'; import 'package:wakelock_web/wakelock_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs // ignore: public_member_api_docs
void registerPlugins(Registrar registrar) { void registerPlugins(Registrar registrar) {
ConnectivityPlugin.registerWith(registrar); ConnectivityPlusPlugin.registerWith(registrar);
FilePickerWeb.registerWith(registrar); FilePickerWeb.registerWith(registrar);
FirebaseCoreWeb.registerWith(registrar); FirebaseCoreWeb.registerWith(registrar);
FirebaseMessagingWeb.registerWith(registrar); FirebaseMessagingWeb.registerWith(registrar);
ImageCropperPlugin.registerWith(registrar);
ImagePickerPlugin.registerWith(registrar); ImagePickerPlugin.registerWith(registrar);
PackageInfoPlugin.registerWith(registrar);
SharedPreferencesPlugin.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar);
UrlLauncherPlugin.registerWith(registrar); UrlLauncherPlugin.registerWith(registrar);
VideoPlayerPlugin.registerWith(registrar); VideoPlayerPlugin.registerWith(registrar);

View File

@ -26,7 +26,7 @@ enum CreateAccountResult {
class AccountHelper { class AccountHelper {
// Current user ID // Current user ID
static int _currentUserID = -1; static int? _currentUserID = -1;
/// Checkout whether current user is signed in or not /// Checkout whether current user is signed in or not
/// ///
@ -67,7 +67,7 @@ class AccountHelper {
} }
// Save current user ID // Save current user ID
final preferences = await PreferencesHelper.getInstance(); final preferences = await (PreferencesHelper.getInstance());
await preferences.setInt(PreferencesKeyList.USER_ID, userID); await preferences.setInt(PreferencesKeyList.USER_ID, userID);
_currentUserID = userID; _currentUserID = userID;
@ -130,14 +130,14 @@ class AccountHelper {
.getObject()["exists"]; .getObject()["exists"];
/// Get current user email address /// Get current user email address
static Future<String> getCurrentAccountEmailAddress() async => static Future<String?> getCurrentAccountEmailAddress() async =>
(await APIRequest.withLogin("account/mail") (await APIRequest.withLogin("account/mail")
.execWithThrowGetObject())["mail"]; .execWithThrowGetObject())["mail"];
/// Check out whether security questions have been set for an account or not /// Check out whether security questions have been set for an account or not
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<bool> hasSecurityQuestions(String email) async => static Future<bool?> hasSecurityQuestions(String email) async =>
(await APIRequest.withoutLogin("account/has_security_questions") (await APIRequest.withoutLogin("account/has_security_questions")
.addString("email", email) .addString("email", email)
.execWithThrow()) .execWithThrow())
@ -146,7 +146,7 @@ class AccountHelper {
/// Get the security questions of the user /// Get the security questions of the user
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<List<String>> getSecurityQuestions(String email) async => static Future<List<String>?> getSecurityQuestions(String? email) async =>
((await APIRequest.withoutLogin("account/get_security_questions") ((await APIRequest.withoutLogin("account/get_security_questions")
.addString("email", email) .addString("email", email)
.execWithThrow()) .execWithThrow())
@ -158,8 +158,8 @@ class AccountHelper {
/// Throws an [Exception] in case of failure /// Throws an [Exception] in case of failure
/// ///
/// Returns a password reset token in case of success /// Returns a password reset token in case of success
static Future<String> checkAnswers( static Future<String?> checkAnswers(
String email, List<String> answers) async => String? email, List<String> answers) async =>
(await APIRequest.withoutLogin("account/check_security_answers") (await APIRequest.withoutLogin("account/check_security_answers")
.addString("email", email) .addString("email", email)
.addString("answers", .addString("answers",
@ -195,7 +195,7 @@ class AccountHelper {
.execWithThrow(); .execWithThrow();
/// Get current user ID from the server /// Get current user ID from the server
Future<int> _downloadCurrentUserID() async { Future<int?> _downloadCurrentUserID() async {
final response = await APIRequest.withLogin("account/id").exec(); final response = await APIRequest.withLogin("account/id").exec();
if (response.code != 200) return null; if (response.code != 200) return null;
@ -210,10 +210,10 @@ class AccountHelper {
} }
/// Check if current user ID is loaded or not /// Check if current user ID is loaded or not
static bool get isUserIDLoaded => _currentUserID > 0; static bool get isUserIDLoaded => _currentUserID! > 0;
/// Get the ID of the currently signed in user /// Get the ID of the currently signed in user
static int getCurrentUserID() { static int? getCurrentUserID() {
if (_currentUserID == -1) throw "Current user ID has not been loaded yet!"; if (_currentUserID == -1) throw "Current user ID has not been loaded yet!";
return _currentUserID; return _currentUserID;
} }

View File

@ -41,13 +41,13 @@ class APIHelper {
else else
url = Uri.https(config().apiServerName, path); url = Uri.https(config().apiServerName, path);
final data = FormData.fromMap(request.args); final data = FormData.fromMap(request.args!);
// Process files (if required) // Process files (if required)
if (multipart) { if (multipart) {
// Process filesystem files // Process filesystem files
for (final key in request.files.keys) { for (final key in request.files.keys) {
var v = request.files[key]; var v = request.files[key]!;
data.files.add(MapEntry( data.files.add(MapEntry(
key, key,
await MultipartFile.fromFile(v.path, await MultipartFile.fromFile(v.path,
@ -56,11 +56,11 @@ class APIHelper {
// Process in-memory files // Process in-memory files
for (final key in request.bytesFiles.keys) { for (final key in request.bytesFiles.keys) {
var v = request.bytesFiles[key]; var v = request.bytesFiles[key]!;
data.files.add(MapEntry( data.files.add(MapEntry(
key, key,
MultipartFile.fromBytes( MultipartFile.fromBytes(
v.bytes, v.bytes!,
filename: v.filename.split("/").last, filename: v.filename.split("/").last,
contentType: v.type, contentType: v.type,
))); )));
@ -85,9 +85,9 @@ class APIHelper {
EventsHelper.emit(InvalidLoginTokensEvent()); EventsHelper.emit(InvalidLoginTokensEvent());
if (response.statusCode != HttpStatus.ok) if (response.statusCode != HttpStatus.ok)
return APIResponse(response.statusCode, response.data); return APIResponse(response.statusCode!, response.data);
return APIResponse(response.statusCode, response.data); return APIResponse(response.statusCode!, response.data);
} catch (e, stack) { } catch (e, stack) {
print(e.toString()); print(e.toString());
print("Could not execute a request!"); print("Could not execute a request!");

View File

@ -39,12 +39,12 @@ class CallsHelper {
.cast<CallMember>()); .cast<CallMember>());
/// Request an offer to access another peer's stream /// Request an offer to access another peer's stream
static Future<void> requestOffer(int callID, int peerID) async => static Future<void> requestOffer(int callID, int? peerID) async =>
await ws("calls/request_offer", {"callID": callID, "peerID": peerID}); await ws("calls/request_offer", {"callID": callID, "peerID": peerID});
/// Send a Session Description message to the server /// Send a Session Description message to the server
static Future<void> sendSessionDescription( static Future<void> sendSessionDescription(
int callID, int peerID, RTCSessionDescription sdp) async => int callID, int? peerID, RTCSessionDescription sdp) async =>
await ws("calls/signal", { await ws("calls/signal", {
"callID": callID, "callID": callID,
"peerID": peerID, "peerID": peerID,
@ -54,7 +54,7 @@ class CallsHelper {
/// Send an IceCandidate /// Send an IceCandidate
static Future<void> sendIceCandidate( static Future<void> sendIceCandidate(
int callID, int peerID, RTCIceCandidate candidate) async => int callID, int? peerID, RTCIceCandidate candidate) async =>
await ws("calls/signal", { await ws("calls/signal", {
"callID": callID, "callID": callID,
"peerID": peerID, "peerID": peerID,

View File

@ -27,7 +27,7 @@ class CommentsHelper {
} }
/// Get a single comment from the server, specified by its [id] /// Get a single comment from the server, specified by its [id]
Future<Comment> getSingle(int id) async { Future<Comment?> getSingle(int id) async {
final response = await APIRequest( final response = await APIRequest(
uri: "comments/get_single", uri: "comments/get_single",
needLogin: true, needLogin: true,
@ -39,7 +39,7 @@ class CommentsHelper {
} }
/// Update comment content /// Update comment content
Future<bool> updateContent(int id, String newContent) async { Future<bool> updateContent(int id, String? newContent) async {
return (await APIRequest(uri: "comments/edit", needLogin: true, args: { return (await APIRequest(uri: "comments/edit", needLogin: true, args: {
"commentID": id.toString(), "commentID": id.toString(),
"content": newContent, "content": newContent,

View File

@ -21,7 +21,6 @@ import 'package:comunic/utils/account_utils.dart';
import 'package:comunic/utils/color_utils.dart'; import 'package:comunic/utils/color_utils.dart';
import 'package:comunic/utils/dart_color.dart'; import 'package:comunic/utils/dart_color.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
/// Conversation helper /// Conversation helper
/// ///
@ -39,7 +38,7 @@ class ConversationsHelper {
/// Throws in case of failure /// Throws in case of failure
static Future<int> createConversation(NewConversation settings) async { static Future<int> createConversation(NewConversation settings) async {
final response = await APIRequest.withLogin("conversations/create", args: { final response = await APIRequest.withLogin("conversations/create", args: {
"name": settings.name ?? "", "name": settings.name,
"follow": settings.follow ? "true" : "false", "follow": settings.follow ? "true" : "false",
"users": settings.members.join(","), "users": settings.members.join(","),
"color": colorToHex(settings.color) "color": colorToHex(settings.color)
@ -53,7 +52,7 @@ class ConversationsHelper {
/// Add a member to a conversation. /// Add a member to a conversation.
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> addMember(int convID, int userID) async => static Future<void> addMember(int? convID, int? userID) async =>
await APIRequest.withLogin("conversations/addMember") await APIRequest.withLogin("conversations/addMember")
.addInt("convID", convID) .addInt("convID", convID)
.addInt("userID", userID) .addInt("userID", userID)
@ -62,7 +61,7 @@ class ConversationsHelper {
/// Remove a member from a conversation. /// Remove a member from a conversation.
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> removeMember(int convID, int userID) async => static Future<void> removeMember(int? convID, int? userID) async =>
await APIRequest.withLogin("conversations/removeMember") await APIRequest.withLogin("conversations/removeMember")
.addInt("convID", convID) .addInt("convID", convID)
.addInt("userID", userID) .addInt("userID", userID)
@ -90,8 +89,8 @@ class ConversationsHelper {
// Update conversation settings // Update conversation settings
if (settings.isComplete) if (settings.isComplete)
request request
.addString("name", settings.name ?? "") .addString("name", settings.name)
.addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers) .addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers!)
.addString("color", colorToHex(settings.color)); .addString("color", colorToHex(settings.color));
await request.execWithThrow(); await request.execWithThrow();
@ -104,7 +103,7 @@ class ConversationsHelper {
/// Set a new conversation logo /// Set a new conversation logo
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> changeImage(int convID, BytesFile file) async => static Future<void> changeImage(int? convID, BytesFile file) async =>
await APIRequest.withLogin("conversations/change_image") await APIRequest.withLogin("conversations/change_image")
.addInt("convID", convID) .addInt("convID", convID)
.addBytesFile("file", file) .addBytesFile("file", file)
@ -113,13 +112,13 @@ class ConversationsHelper {
/// Remove conversation logo /// Remove conversation logo
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> removeLogo(int convID) async => static Future<void> removeLogo(int? convID) async =>
await APIRequest.withLogin("conversations/delete_image") await APIRequest.withLogin("conversations/delete_image")
.addInt("convID", convID) .addInt("convID", convID)
.execWithThrow(); .execWithThrow();
/// Delete a conversation specified by its [id] /// Delete a conversation specified by its [id]
Future<void> deleteConversation(int id) async => Future<void> deleteConversation(int? id) async =>
await APIRequest.withLogin("conversations/delete") await APIRequest.withLogin("conversations/delete")
.addInt("conversationID", id) .addInt("conversationID", id)
.execWithThrow(); .execWithThrow();
@ -132,7 +131,7 @@ class ConversationsHelper {
await APIRequest.withLogin("conversations/getList").execWithThrow(); await APIRequest.withLogin("conversations/getList").execWithThrow();
ConversationsList list = ConversationsList(); ConversationsList list = ConversationsList();
response.getArray().forEach((f) => list.add(apiToConversation(f))); response.getArray()!.forEach((f) => list.add(apiToConversation(f)));
// Update the database // Update the database
await ConversationsSerializationHelper().setList(list); await ConversationsSerializationHelper().setList(list);
@ -148,7 +147,7 @@ class ConversationsHelper {
} }
/// Get information about a single conversation specified by its [id] /// Get information about a single conversation specified by its [id]
Future<Conversation> _downloadSingle(int id) async { Future<Conversation> _downloadSingle(int? id) async {
final response = await APIRequest( final response = await APIRequest(
uri: "conversations/get_single", uri: "conversations/get_single",
needLogin: true, needLogin: true,
@ -167,7 +166,7 @@ class ConversationsHelper {
/// case of failure /// case of failure
/// ///
/// Return value of this method is never null. /// Return value of this method is never null.
Future<Conversation> getSingle(int id, {bool force = false}) async { Future<Conversation> getSingle(int? id, {bool force = false}) async {
if (force || if (force ||
!await ConversationsSerializationHelper().any((c) => c.id == id)) !await ConversationsSerializationHelper().any((c) => c.id == id))
return await _downloadSingle(id); return await _downloadSingle(id);
@ -178,19 +177,19 @@ class ConversationsHelper {
/// Get the name of a [conversation]. This requires information /// Get the name of a [conversation]. This requires information
/// about the users of this conversation /// about the users of this conversation
static String getConversationName( static String getConversationName(
Conversation conversation, UsersList users) { Conversation conversation, UsersList? users) {
if (conversation.hasName) return conversation.name; if (conversation.hasName) return conversation.name!;
String name = ""; String name = "";
int count = 0; int count = 0;
for (int i = 0; i < 3 && i < conversation.members.length; i++) for (int i = 0; i < 3 && i < conversation.members!.length; i++)
if (conversation.members[i].userID != userID()) { if (conversation.members![i].userID != userID()) {
name += (count > 0 ? ", " : "") + name += (count > 0 ? ", " : "") +
users.getUser(conversation.members[i].userID).fullName; users!.getUser(conversation.members![i].userID).fullName;
count++; count++;
} }
if (conversation.members.length > 3) name += ", ..."; if (conversation.members!.length > 3) name += ", ...";
return name; return name;
} }
@ -200,7 +199,7 @@ class ConversationsHelper {
/// true /// true
/// ///
/// Throws an exception in case of failure /// Throws an exception in case of failure
Future<int> getPrivate(int userID, {bool allowCreate = true}) async { Future<int> getPrivate(int? userID, {bool allowCreate = true}) async {
final response = await APIRequest( final response = await APIRequest(
uri: "conversations/getPrivate", uri: "conversations/getPrivate",
needLogin: true, needLogin: true,
@ -222,7 +221,7 @@ class ConversationsHelper {
/// Throws an exception in case of failure /// Throws an exception in case of failure
static Future<String> getConversationNameAsync( static Future<String> getConversationNameAsync(
Conversation conversation) async { Conversation conversation) async {
if (conversation.hasName) return conversation.name; if (conversation.hasName) return conversation.name!;
//Get information about the members of the conversation //Get information about the members of the conversation
final members = await UsersHelper().getList(conversation.membersID); final members = await UsersHelper().getList(conversation.membersID);
@ -273,7 +272,7 @@ class ConversationsHelper {
// Parse the response of the server // Parse the response of the server
ConversationMessagesList list = ConversationMessagesList(); ConversationMessagesList list = ConversationMessagesList();
response.getArray().forEach((f) { response.getArray()!.forEach((f) {
list.add( list.add(
apiToConversationMessage(f), apiToConversationMessage(f),
); );
@ -294,7 +293,7 @@ class ConversationsHelper {
/// Throws an exception in case of failure /// Throws an exception in case of failure
Future<ConversationMessagesList> _downloadNewMessagesSingle( Future<ConversationMessagesList> _downloadNewMessagesSingle(
int conversationID, int conversationID,
{int lastMessageID = 0}) async { {int? lastMessageID = 0}) async {
// Execute the request on the server // Execute the request on the server
final response = await APIRequest( final response = await APIRequest(
uri: "conversations/refresh_single", uri: "conversations/refresh_single",
@ -311,8 +310,8 @@ class ConversationsHelper {
/// ///
/// Throws in case of failure /// Throws in case of failure
Future<ConversationMessagesList> getOlderMessages({ Future<ConversationMessagesList> getOlderMessages({
@required int conversationID, required int conversationID,
@required int oldestMessagesID, required int? oldestMessagesID,
int limit = 15, int limit = 15,
}) async { }) async {
// Perform the request online // Perform the request online
@ -334,8 +333,8 @@ class ConversationsHelper {
/// ///
/// Throws in case of failure /// Throws in case of failure
Future<ConversationMessagesList> getNewMessages( Future<ConversationMessagesList> getNewMessages(
{@required int conversationID, {required int conversationID,
int lastMessageID = 0, int? lastMessageID = 0,
bool online = true}) async { bool online = true}) async {
if (online) if (online)
return await _downloadNewMessagesSingle(conversationID, return await _downloadNewMessagesSingle(conversationID,
@ -348,8 +347,8 @@ class ConversationsHelper {
/// Send a new message to the server /// Send a new message to the server
Future<SendMessageResult> sendMessage( Future<SendMessageResult> sendMessage(
NewConversationMessage message, { NewConversationMessage message, {
ProgressCallback sendProgress, ProgressCallback? sendProgress,
CancelToken cancelToken, CancelToken? cancelToken,
}) async { }) async {
final request = APIRequest.withLogin("conversations/sendMessage") final request = APIRequest.withLogin("conversations/sendMessage")
.addInt("conversationID", message.conversationID) .addInt("conversationID", message.conversationID)
@ -388,7 +387,7 @@ class ConversationsHelper {
await ConversationsMessagesSerializationHelper(msg.convID).remove(msg); await ConversationsMessagesSerializationHelper(msg.convID).remove(msg);
/// Update a message content /// Update a message content
Future<bool> updateMessage(int id, String newContent) async { Future<bool> updateMessage(int? id, String newContent) async {
final response = await APIRequest( final response = await APIRequest(
uri: "conversations/updateMessage", uri: "conversations/updateMessage",
needLogin: true, needLogin: true,
@ -400,7 +399,7 @@ class ConversationsHelper {
} }
/// Delete permanently a message specified by its [id] /// Delete permanently a message specified by its [id]
Future<bool> deleteMessage(int id) async { Future<bool> deleteMessage(int? id) async {
// Delete the message online // Delete the message online
final response = await APIRequest( final response = await APIRequest(
uri: "conversations/deleteMessage", uri: "conversations/deleteMessage",
@ -418,7 +417,7 @@ class ConversationsHelper {
static Future<UnreadConversationsList> getListUnread() async { static Future<UnreadConversationsList> getListUnread() async {
final list = (await APIRequest.withLogin("conversations/get_list_unread") final list = (await APIRequest.withLogin("conversations/get_list_unread")
.execWithThrow()) .execWithThrow())
.getArray(); .getArray()!;
return UnreadConversationsList() return UnreadConversationsList()
..addAll(list.map((f) => UnreadConversation( ..addAll(list.map((f) => UnreadConversation(
@ -431,7 +430,7 @@ class ConversationsHelper {
/// conversation through WebSocket /// conversation through WebSocket
Future<void> registerConversationEvents(int id) async { Future<void> registerConversationEvents(int id) async {
if (_registeredConversations.containsKey(id)) if (_registeredConversations.containsKey(id))
_registeredConversations[id]++; _registeredConversations.update(id, (value) => value + 1);
else { else {
_registeredConversations[id] = 1; _registeredConversations[id] = 1;
await ws("\$main/register_conv", {"convID": id}); await ws("\$main/register_conv", {"convID": id});
@ -442,16 +441,16 @@ class ConversationsHelper {
Future<void> unregisterConversationEvents(int id) async { Future<void> unregisterConversationEvents(int id) async {
if (!_registeredConversations.containsKey(id)) return; if (!_registeredConversations.containsKey(id)) return;
_registeredConversations[id]--; _registeredConversations.update(id, (value) => value - 1);
if (_registeredConversations[id] <= 0) { if (_registeredConversations[id]! <= 0) {
_registeredConversations.remove(id); _registeredConversations.remove(id);
await ws("\$main/unregister_conv", {"convID": id}); await ws("\$main/unregister_conv", {"convID": id});
} }
} }
/// Send a notification to inform that the user is writing a message /// Send a notification to inform that the user is writing a message
static Future<void> sendWritingEvent(int convID) async => static Future<void> sendWritingEvent(int? convID) async =>
await ws("conversations/is_writing", {"convID": convID}); await ws("conversations/is_writing", {"convID": convID});
/// Turn an API response into a ConversationMessage object /// Turn an API response into a ConversationMessage object

View File

@ -9,11 +9,11 @@ import 'package:sqflite/sqflite.dart';
/// @author Pierre HUBERT /// @author Pierre HUBERT
abstract class DatabaseHelper { abstract class DatabaseHelper {
static Database _db; static Database? _db;
/// Open the database /// Open the database
static Future<void> open() async { static Future<void> open() async {
if (_db != null && _db.isOpen) return; if (_db != null && _db!.isOpen) return;
var databasePath = await getDatabasesPath(); var databasePath = await getDatabasesPath();
_db = await openDatabase( _db = await openDatabase(
@ -24,7 +24,7 @@ abstract class DatabaseHelper {
} }
/// Get a database instance /// Get a database instance
static Future<Database> get() async { static Future<Database?> get() async {
await open(); await open();
return _db; return _db;
} }

View File

@ -18,12 +18,12 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
/// Insert an entry in the database /// Insert an entry in the database
Future<void> _insertDB(T el) async { Future<void> _insertDB(T el) async {
await (await DatabaseHelper.get()).insert(tableName(), el.toMap()); await (await DatabaseHelper.get())!.insert(tableName(), el.toMap());
} }
/// Update an element in the database /// Update an element in the database
Future<void> _updateDB(T el) async { Future<void> _updateDB(T el) async {
await (await DatabaseHelper.get()).update( await (await DatabaseHelper.get())!.update(
tableName(), tableName(),
el.toMap(), el.toMap(),
where: "${BaseTableContract.C_ID} = ?", where: "${BaseTableContract.C_ID} = ?",
@ -34,14 +34,14 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
/// Get an element from the database with a specified [id] /// Get an element from the database with a specified [id]
/// ///
/// Returns null if none found /// Returns null if none found
Future<T> get(int id) async { Future<T?> get(int id) async {
List<Map> maps = await (await DatabaseHelper.get()).query( List<Map> maps = await (await DatabaseHelper.get())!.query(
tableName(), tableName(),
where: '${BaseTableContract.C_ID} = ?', where: '${BaseTableContract.C_ID} = ?',
whereArgs: [id], whereArgs: [id],
); );
if (maps.length > 0) return initializeFromMap(maps[0]); if (maps.length > 0) return initializeFromMap(maps[0] as Map<String, dynamic>);
return null; return null;
} }
@ -50,7 +50,7 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
/// ///
/// Return true if at least one entry was deleted / false else /// Return true if at least one entry was deleted / false else
Future<bool> delete(int id) async { Future<bool> delete(int id) async {
return await (await DatabaseHelper.get()).delete( return await (await DatabaseHelper.get())!.delete(
tableName(), tableName(),
where: '${BaseTableContract.C_ID} = ?', where: '${BaseTableContract.C_ID} = ?',
whereArgs: [id], whereArgs: [id],
@ -59,22 +59,22 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
/// Get all the entries from the table /// Get all the entries from the table
Future<List<T>> getAll() async { Future<List<T>> getAll() async {
List<Map> maps = await (await DatabaseHelper.get()).query(tableName()); List<Map> maps = await (await DatabaseHelper.get())!.query(tableName());
return maps.map((f) => initializeFromMap(f)).toList(); return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
} }
/// Get some entries from the table based on some conditions /// Get some entries from the table based on some conditions
Future<List<T>> getMultiple( Future<List<T>> getMultiple(
{bool distinct, {bool? distinct,
List<String> columns, List<String>? columns,
String where, String? where,
List<dynamic> whereArgs, List<dynamic>? whereArgs,
String groupBy, String? groupBy,
String having, String? having,
String orderBy, String? orderBy,
int limit, int? limit,
int offset}) async { int? offset}) async {
List<Map> maps = await (await DatabaseHelper.get()).query( List<Map> maps = await (await DatabaseHelper.get())!.query(
tableName(), tableName(),
distinct: distinct, distinct: distinct,
columns: columns, columns: columns,
@ -86,12 +86,12 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
limit: limit, limit: limit,
offset: offset, offset: offset,
); );
return maps.map((f) => initializeFromMap(f)).toList(); return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
} }
/// Empty the table /// Empty the table
Future<void> clearTable() async { Future<void> clearTable() async {
await (await DatabaseHelper.get()).execute("DELETE FROM ${tableName()}"); await (await DatabaseHelper.get())!.execute("DELETE FROM ${tableName()}");
} }
/// Check out whether an element specified with its [id] is present /// Check out whether an element specified with its [id] is present

View File

@ -17,14 +17,14 @@ class WSClosedEvent {}
/// New number of notifications /// New number of notifications
class NewNumberNotifsEvent { class NewNumberNotifsEvent {
final int newNum; final int? newNum;
NewNumberNotifsEvent(this.newNum); NewNumberNotifsEvent(this.newNum);
} }
/// New number of unread conversations /// New number of unread conversations
class NewNumberUnreadConversations { class NewNumberUnreadConversations {
final int newNum; final int? newNum;
NewNumberUnreadConversations(this.newNum); NewNumberUnreadConversations(this.newNum);
} }
@ -45,15 +45,15 @@ class UpdatedCommentEvent {
/// Deleted comment /// Deleted comment
class DeletedCommentEvent { class DeletedCommentEvent {
final int commentID; final int? commentID;
DeletedCommentEvent(this.commentID); DeletedCommentEvent(this.commentID);
} }
/// Writing message in conversation event /// Writing message in conversation event
class WritingMessageInConversationEvent { class WritingMessageInConversationEvent {
final int convID; final int? convID;
final int userID; final int? userID;
WritingMessageInConversationEvent(this.convID, this.userID); WritingMessageInConversationEvent(this.convID, this.userID);
} }
@ -81,31 +81,31 @@ class DeletedConversationMessageEvent {
/// Remove user from conversation /// Remove user from conversation
class RemovedUserFromConversationEvent { class RemovedUserFromConversationEvent {
final int convID; final int? convID;
final int userID; final int? userID;
RemovedUserFromConversationEvent(this.convID, this.userID); RemovedUserFromConversationEvent(this.convID, this.userID);
} }
/// Deleted conversation /// Deleted conversation
class DeletedConversationEvent { class DeletedConversationEvent {
final int convID; final int? convID;
DeletedConversationEvent(this.convID); DeletedConversationEvent(this.convID);
} }
/// User joined call event /// User joined call event
class UserJoinedCallEvent { class UserJoinedCallEvent {
final int callID; final int? callID;
final int userID; final int? userID;
UserJoinedCallEvent(this.callID, this.userID); UserJoinedCallEvent(this.callID, this.userID);
} }
/// User left call event /// User left call event
class UserLeftCallEvent { class UserLeftCallEvent {
final int callID; final int? callID;
final int userID; final int? userID;
UserLeftCallEvent(this.callID, this.userID); UserLeftCallEvent(this.callID, this.userID);
} }
@ -114,38 +114,36 @@ class UserLeftCallEvent {
class NewCallSignalEvent { class NewCallSignalEvent {
final int callID; final int callID;
final int peerID; final int peerID;
final RTCSessionDescription sessionDescription; final RTCSessionDescription? sessionDescription;
final RTCIceCandidate candidate; final RTCIceCandidate? candidate;
const NewCallSignalEvent({ const NewCallSignalEvent({
this.callID, required this.callID,
this.peerID, required this.peerID,
this.sessionDescription, this.sessionDescription,
this.candidate, this.candidate,
}) : assert(callID != null), }) : assert(sessionDescription != null || candidate != null);
assert(peerID != null),
assert(sessionDescription != null || candidate != null);
} }
/// Call peer ready event /// Call peer ready event
class CallPeerReadyEvent { class CallPeerReadyEvent {
final int callID; final int? callID;
final int peerID; final int? peerID;
CallPeerReadyEvent(this.callID, this.peerID); CallPeerReadyEvent(this.callID, this.peerID);
} }
/// Call peer interrupted streaming event /// Call peer interrupted streaming event
class CallPeerInterruptedStreamingEvent { class CallPeerInterruptedStreamingEvent {
final int callID; final int? callID;
final int peerID; final int? peerID;
CallPeerInterruptedStreamingEvent(this.callID, this.peerID); CallPeerInterruptedStreamingEvent(this.callID, this.peerID);
} }
/// Call closed event /// Call closed event
class CallClosedEvent { class CallClosedEvent {
final int callID; final int? callID;
CallClosedEvent(this.callID); CallClosedEvent(this.callID);
} }

View File

@ -20,7 +20,7 @@ class FirebaseMessagingHelper {
} }
/// Get a Firebase access token /// Get a Firebase access token
static Future<String> getToken() async { static Future<String?> getToken() async {
return await FirebaseMessaging.instance.getToken(); return await FirebaseMessaging.instance.getToken();
} }
} }

View File

@ -11,7 +11,7 @@ import 'package:comunic/models/group.dart';
class ForezGroupsHelper { class ForezGroupsHelper {
static Future<List<Group>> getForezGroups() async { static Future<List<Group>> getForezGroups() async {
return (await APIRequest.withLogin("forez/get_groups").execWithThrow()) return (await APIRequest.withLogin("forez/get_groups").execWithThrow())
.getArray() .getArray()!
.cast<Map<String, dynamic>>() .cast<Map<String, dynamic>>()
.map(GroupsHelper.getGroupFromAPI) .map(GroupsHelper.getGroupFromAPI)
.toList(); .toList();
@ -21,10 +21,10 @@ class ForezGroupsHelper {
/// ///
/// This methods throws an exception in case of failure /// This methods throws an exception in case of failure
static Future<AdvancedUserInfo> getMemberInfo(int groupID, int userID) async { static Future<AdvancedUserInfo> getMemberInfo(int groupID, int userID) async {
final response = await APIRequest.withLogin("forez/get_member_info") final response = await (APIRequest.withLogin("forez/get_member_info")
.addInt("group", groupID) .addInt("group", groupID)
.addInt("user", userID) .addInt("user", userID)
.execWithThrowGetObject(); .execWithThrowGetObject());
return UsersHelper.apiToAdvancedUserInfo(response); return UsersHelper.apiToAdvancedUserInfo(response);
} }

View File

@ -6,8 +6,8 @@ import 'package:comunic/models/forez_presence.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
int _cachedGroup; int? _cachedGroup;
PresenceSet _cache; PresenceSet? _cache;
class ForezPresenceHelper { class ForezPresenceHelper {
/// Refresh presence cache /// Refresh presence cache
@ -40,16 +40,16 @@ class ForezPresenceHelper {
/// Get the presences of a given user /// Get the presences of a given user
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<PresenceSet> getForUser(int groupID, int userID) async { static Future<PresenceSet> getForUser(int groupID, int? userID) async {
await _checkCache(groupID); await _checkCache(groupID);
return _cache.getForUser(userID); return _cache!.getForUser(userID);
} }
/// Get all the available presences /// Get all the available presences
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<PresenceSet> getAll(int groupID) async { static Future<PresenceSet?> getAll(int groupID) async {
await _checkCache(groupID); await _checkCache(groupID);
return _cache; return _cache;
} }

View File

@ -3,7 +3,6 @@ import 'package:comunic/lists/friends_list.dart';
import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/friend.dart'; import 'package:comunic/models/friend.dart';
import 'package:comunic/models/friend_status.dart'; import 'package:comunic/models/friend_status.dart';
import 'package:meta/meta.dart';
/// Friends helper /// Friends helper
/// ///
@ -16,7 +15,7 @@ class FriendsHelper {
/// ///
/// Returns the list of friends in case of success, or null if an error /// Returns the list of friends in case of success, or null if an error
/// occurred /// occurred
Future<FriendsList> _downloadList() async { Future<FriendsList?> _downloadList() async {
final response = await APIRequest( final response = await APIRequest(
uri: "friends/getList", uri: "friends/getList",
needLogin: true, needLogin: true,
@ -30,7 +29,7 @@ class FriendsHelper {
// Parse and return the list of friends // Parse and return the list of friends
FriendsList list = FriendsList() FriendsList list = FriendsList()
..addAll(response ..addAll(response
.getArray() .getArray()!
.cast<Map<String, dynamic>>() .cast<Map<String, dynamic>>()
.map(apiToFriend) .map(apiToFriend)
.toList()); .toList());
@ -54,7 +53,7 @@ class FriendsHelper {
} }
/// Get the list, either from an online or an offline source /// Get the list, either from an online or an offline source
Future<FriendsList> getList({@required bool online}) async { Future<FriendsList?> getList({required bool online}) async {
if (online) if (online)
return await _downloadList(); return await _downloadList();
else else
@ -132,7 +131,7 @@ class FriendsHelper {
if (response.code != 200) if (response.code != 200)
throw new Exception("Could not get the list of friends of this user!"); throw new Exception("Could not get the list of friends of this user!");
return Set<int>.from(response.getArray()); return Set<int>.from(response.getArray()!);
} }
/// Send a friendship request to a specified user /// Send a friendship request to a specified user

View File

@ -49,15 +49,15 @@ enum GetAdvancedInfoStatus { SUCCESS, ACCESS_DENIED }
class GetAdvancedInfoResult { class GetAdvancedInfoResult {
final GetAdvancedInfoStatus status; final GetAdvancedInfoStatus status;
final AdvancedGroupInfo info; final AdvancedGroupInfo? info;
GetAdvancedInfoResult(this.status, this.info) : assert(status != null); GetAdvancedInfoResult(this.status, this.info);
} }
/// Groups helper /// Groups helper
class GroupsHelper { class GroupsHelper {
/// Download a list of groups information from the server /// Download a list of groups information from the server
Future<GroupsList> _downloadList(Set<int> groups) async { Future<GroupsList?> _downloadList(Set<int?> groups) async {
final response = await APIRequest( final response = await APIRequest(
uri: "groups/get_multiple_info", uri: "groups/get_multiple_info",
needLogin: true, needLogin: true,
@ -77,7 +77,7 @@ class GroupsHelper {
/// Get a list of groups from the server. In case of error, this method throws /// Get a list of groups from the server. In case of error, this method throws
/// an exception /// an exception
Future<GroupsList> getListOrThrow(Set<int> groups, Future<GroupsList> getListOrThrow(Set<int?> groups,
{bool force = false}) async { {bool force = false}) async {
final list = await getList(groups, force: force); final list = await getList(groups, force: force);
@ -87,11 +87,11 @@ class GroupsHelper {
} }
/// Get a list of groups from the server /// Get a list of groups from the server
Future<GroupsList> getList(Set<int> groups, {bool force = false}) async { Future<GroupsList?> getList(Set<int?> groups, {bool force = false}) async {
final list = GroupsList(); final list = GroupsList();
// Check which groups information to download // Check which groups information to download
final toDownload = Set<int>(); final toDownload = Set<int?>();
groups.forEach((groupID) { groups.forEach((groupID) {
if (!force && _groupsListCache.containsKey(groupID)) if (!force && _groupsListCache.containsKey(groupID))
list[groupID] = _groupsListCache[groupID]; list[groupID] = _groupsListCache[groupID];
@ -122,10 +122,10 @@ class GroupsHelper {
} }
/// Get the list of groups of a user /// Get the list of groups of a user
Future<Set<int>> getListUser() async => Future<Set<int?>> getListUser() async =>
(await APIRequest(uri: "groups/get_my_list", needLogin: true).exec()) (await APIRequest(uri: "groups/get_my_list", needLogin: true).exec())
.assertOk() .assertOk()
.getArray() .getArray()!
.map((f) => cast<int>(f)) .map((f) => cast<int>(f))
.toSet(); .toSet();
@ -142,7 +142,7 @@ class GroupsHelper {
/// Perform a simple membership request /// Perform a simple membership request
static Future<bool> _simpleMembershipRequest(int groupID, String uri, static Future<bool> _simpleMembershipRequest(int groupID, String uri,
{Map<String, String> args}) async => {Map<String, String>? args}) async =>
(await (APIRequest.withLogin(uri) (await (APIRequest.withLogin(uri)
..addInt("id", groupID) ..addInt("id", groupID)
..addArgs(args == null ? Map() : args)) ..addArgs(args == null ? Map() : args))
@ -176,7 +176,7 @@ class GroupsHelper {
.isOK; .isOK;
/// Get advanced information about the user /// Get advanced information about the user
Future<GetAdvancedInfoResult> getAdvancedInfo(int groupID) async { Future<GetAdvancedInfoResult> getAdvancedInfo(int? groupID) async {
// Get advanced information about the user // Get advanced information about the user
final result = final result =
await (APIRequest(uri: "groups/get_advanced_info", needLogin: true) await (APIRequest(uri: "groups/get_advanced_info", needLogin: true)
@ -202,7 +202,7 @@ class GroupsHelper {
/// change in the future /// change in the future
/// ///
/// Throws in case of error /// Throws in case of error
Future<AdvancedGroupInfo> getSettings(int groupID) async { Future<AdvancedGroupInfo?> getSettings(int groupID) async {
final groupInfo = await getAdvancedInfo(groupID); final groupInfo = await getAdvancedInfo(groupID);
if (groupInfo.status != GetAdvancedInfoStatus.SUCCESS) if (groupInfo.status != GetAdvancedInfoStatus.SUCCESS)
@ -239,7 +239,7 @@ class GroupsHelper {
"posts_level", "posts_level",
invertMap( invertMap(
_APIGroupsPostsCreationLevelsMap)[settings.postCreationLevel]) _APIGroupsPostsCreationLevelsMap)[settings.postCreationLevel])
.addBool("is_members_list_public", settings.isMembersListPublic) .addBool("is_members_list_public", settings.isMembersListPublic!)
.addString("description", settings.description) .addString("description", settings.description)
.addString("url", settings.url) .addString("url", settings.url)
.execWithThrow(); .execWithThrow();
@ -248,7 +248,7 @@ class GroupsHelper {
/// Upload a new logo /// Upload a new logo
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> uploadNewLogo(int groupID, Uint8List bytes) async => static Future<void> uploadNewLogo(int groupID, Uint8List? bytes) async =>
await APIRequest(uri: "groups/upload_logo", needLogin: true) await APIRequest(uri: "groups/upload_logo", needLogin: true)
.addInt("id", groupID) .addInt("id", groupID)
.addBytesFile("logo", BytesFile("logo.png", bytes)) .addBytesFile("logo", BytesFile("logo.png", bytes))
@ -279,7 +279,7 @@ class GroupsHelper {
..addAll((await APIRequest(uri: "groups/get_members", needLogin: true) ..addAll((await APIRequest(uri: "groups/get_members", needLogin: true)
.addInt("id", groupID) .addInt("id", groupID)
.execWithThrow()) .execWithThrow())
.getArray() .getArray()!
.map((f) => _apiToGroupMembership(f)) .map((f) => _apiToGroupMembership(f))
.toList()); .toList());
@ -295,7 +295,7 @@ class GroupsHelper {
/// Cancel a group membership invitation /// Cancel a group membership invitation
/// ///
/// Throws an exception in case of failure /// Throws an exception in case of failure
static Future<void> cancelInvitation(int groupID, int userID) async => static Future<void> cancelInvitation(int groupID, int? userID) async =>
await APIRequest.withLogin("groups/cancel_invitation") await APIRequest.withLogin("groups/cancel_invitation")
.addInt("groupID", groupID) .addInt("groupID", groupID)
.addInt("userID", userID) .addInt("userID", userID)
@ -305,7 +305,7 @@ class GroupsHelper {
/// ///
/// Throws an exception in case of failure /// Throws an exception in case of failure
static Future<void> respondRequest( static Future<void> respondRequest(
int groupID, int userID, bool accept) async => int groupID, int? userID, bool accept) async =>
await APIRequest.withLogin("groups/respond_request") await APIRequest.withLogin("groups/respond_request")
.addInt("groupID", groupID) .addInt("groupID", groupID)
.addInt("userID", userID) .addInt("userID", userID)
@ -351,7 +351,7 @@ class GroupsHelper {
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> setConversationVisibility( static Future<void> setConversationVisibility(
int convID, GroupMembershipLevel newLevel) async => int? convID, GroupMembershipLevel? newLevel) async =>
await APIRequest.withLogin("groups/set_conversation_visibility") await APIRequest.withLogin("groups/set_conversation_visibility")
.addInt("conv_id", convID) .addInt("conv_id", convID)
.addString( .addString(
@ -364,7 +364,7 @@ class GroupsHelper {
/// Delete a group's conversation /// Delete a group's conversation
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> deleteConversation(int convID) async => static Future<void> deleteConversation(int? convID) async =>
await APIRequest.withLogin("groups/delete_conversation") await APIRequest.withLogin("groups/delete_conversation")
.addInt("conv_id", convID) .addInt("conv_id", convID)
.execWithThrow(); .execWithThrow();
@ -376,11 +376,11 @@ class GroupsHelper {
name: map["name"], name: map["name"],
iconURL: map["icon_url"], iconURL: map["icon_url"],
numberMembers: map["number_members"], numberMembers: map["number_members"],
membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]], membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!,
visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]], visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!,
registrationLevel: registrationLevel:
_APIGroupsRegistrationLevelsMap[map["registration_level"]], _APIGroupsRegistrationLevelsMap[map["registration_level"]]!,
postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]], postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!,
virtualDirectory: nullToEmpty(map["virtual_directory"]), virtualDirectory: nullToEmpty(map["virtual_directory"]),
following: map["following"]); following: map["following"]);
} }
@ -392,11 +392,11 @@ class GroupsHelper {
name: map["name"], name: map["name"],
iconURL: map["icon_url"], iconURL: map["icon_url"],
numberMembers: map["number_members"], numberMembers: map["number_members"],
membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]], membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!,
visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]], visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!,
registrationLevel: registrationLevel:
_APIGroupsRegistrationLevelsMap[map["registration_level"]], _APIGroupsRegistrationLevelsMap[map["registration_level"]]!,
postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]], postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!,
isMembersListPublic: map["is_members_list_public"], isMembersListPublic: map["is_members_list_public"],
virtualDirectory: nullToEmpty(map["virtual_directory"]), virtualDirectory: nullToEmpty(map["virtual_directory"]),
following: map["following"], following: map["following"],
@ -418,6 +418,6 @@ class GroupsHelper {
userID: row["user_id"], userID: row["user_id"],
groupID: row["group_id"], groupID: row["group_id"],
timeCreate: row["time_create"], timeCreate: row["time_create"],
level: APIGroupsMembershipLevelsMap[row["level"]], level: APIGroupsMembershipLevelsMap[row["level"]]!,
); );
} }

View File

@ -29,7 +29,7 @@ class IndependentPushNotificationsHelper {
} }
/// Configure independent push notification services with a pull URL /// Configure independent push notification services with a pull URL
static Future<void> configure(String wsURL) async { static Future<void> configure(String? wsURL) async {
await platform.invokeMethod("configure", wsURL); await platform.invokeMethod("configure", wsURL);
} }

View File

@ -1,6 +1,5 @@
import 'package:comunic/enums/likes_type.dart'; import 'package:comunic/enums/likes_type.dart';
import 'package:comunic/helpers/websocket_helper.dart'; import 'package:comunic/helpers/websocket_helper.dart';
import 'package:meta/meta.dart';
/// Likes helper /// Likes helper
/// ///
@ -16,9 +15,9 @@ const LikesAPIMap = {
class LikesHelper { class LikesHelper {
/// Update liking status of an element /// Update liking status of an element
Future<void> setLiking({ Future<void> setLiking({
@required LikesType type, required LikesType type,
@required bool like, required bool like,
@required int id, required int id,
}) async { }) async {
return (await ws("likes/update", { return (await ws("likes/update", {
"type": LikesAPIMap[type], "type": LikesAPIMap[type],

View File

@ -75,15 +75,15 @@ class NotificationsHelper {
// Parse the list of notifications // Parse the list of notifications
return NotificationsList() return NotificationsList()
..addAll(response ..addAll(response
.getArray() .getArray()!
.map((f) => Notification( .map((f) => Notification(
id: f["id"], id: f["id"],
timeCreate: f["time_create"], timeCreate: f["time_create"],
seen: f["seen"], seen: f["seen"],
fromUser: f["from_user_id"], fromUser: f["from_user_id"],
onElemId: f["on_elem_id"], onElemId: f["on_elem_id"],
onElemType: _NotificationElementTypeAPImapping[f["on_elem_type"]], onElemType: _NotificationElementTypeAPImapping[f["on_elem_type"]]!,
type: _NotificationsTypeAPImapping[f["type"]], type: _NotificationsTypeAPImapping[f["type"]]!,
fromContainerId: f["from_container_id"], fromContainerId: f["from_container_id"],
fromContainerType: f["from_container_type"] == "" fromContainerType: f["from_container_type"] == ""
? null ? null

View File

@ -55,7 +55,7 @@ class PostsHelper {
/// Get the list of latest posts. Return the list of posts or null in case of /// Get the list of latest posts. Return the list of posts or null in case of
/// failure /// failure
Future<PostsList> getLatest({int from = 0}) async { Future<PostsList?> getLatest({int from = 0}) async {
final response = final response =
await APIRequest(uri: "posts/get_latest", needLogin: true, args: { await APIRequest(uri: "posts/get_latest", needLogin: true, args: {
"include_groups": true.toString(), "include_groups": true.toString(),
@ -66,7 +66,8 @@ class PostsHelper {
try { try {
// Parse & return the list of posts // Parse & return the list of posts
return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f))); return PostsList()
..addAll(response.getArray()!.map((f) => _apiToPost(f)));
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
@ -74,7 +75,7 @@ class PostsHelper {
} }
/// Get the list of posts of a user /// Get the list of posts of a user
Future<PostsList> getUserPosts(int userID, {int from = 0}) async { Future<PostsList?> getUserPosts(int? userID, {int from = 0}) async {
final response = await (APIRequest(uri: "posts/get_user", needLogin: true) final response = await (APIRequest(uri: "posts/get_user", needLogin: true)
..addInt("userID", userID) ..addInt("userID", userID)
..addInt("startFrom", from == 0 ? 0 : from - 1)) ..addInt("startFrom", from == 0 ? 0 : from - 1))
@ -84,7 +85,8 @@ class PostsHelper {
try { try {
// Parse & return the list of posts // Parse & return the list of posts
return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f))); return PostsList()
..addAll(response.getArray()!.map((f) => _apiToPost(f)));
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
@ -92,7 +94,7 @@ class PostsHelper {
} }
/// Get the list of posts of a group /// Get the list of posts of a group
Future<PostsList> getGroupPosts(int groupID, {int from = 0}) async { Future<PostsList?> getGroupPosts(int groupID, {int from = 0}) async {
final response = await (APIRequest(uri: "posts/get_group", needLogin: true) final response = await (APIRequest(uri: "posts/get_group", needLogin: true)
..addInt("groupID", groupID) ..addInt("groupID", groupID)
..addInt("startFrom", from == 0 ? 0 : from - 1)) ..addInt("startFrom", from == 0 ? 0 : from - 1))
@ -102,7 +104,8 @@ class PostsHelper {
try { try {
// Parse & return the list of posts // Parse & return the list of posts
return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f))); return PostsList()
..addAll(response.getArray()!.map((f) => _apiToPost(f)));
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
@ -158,13 +161,14 @@ class PostsHelper {
case PostKind.COUNTDOWN: case PostKind.COUNTDOWN:
request.addInt( request.addInt(
"time-end", (post.timeEnd.millisecondsSinceEpoch / 1000).floor()); "time-end", (post.timeEnd!.millisecondsSinceEpoch / 1000).floor());
break; break;
case PostKind.SURVEY: case PostKind.SURVEY:
request.addString("question", post.survey.question); request.addString("question", post.survey!.question);
request.addString("answers", post.survey.answers.join("<>")); request.addString("answers", post.survey!.answers.join("<>"));
request.addBool("allowNewAnswers", post.survey.allowNewChoicesCreation); request.addBool(
"allowNewAnswers", post.survey!.allowNewChoicesCreation);
break; break;
case PostKind.YOUTUBE: case PostKind.YOUTUBE:
@ -173,7 +177,6 @@ class PostsHelper {
default: default:
throw Exception("Unsupported post type :" + post.kind.toString()); throw Exception("Unsupported post type :" + post.kind.toString());
break;
} }
final response = await request.execWithFiles(); final response = await request.execWithFiles();
@ -221,7 +224,7 @@ class PostsHelper {
/// Register to a post events /// Register to a post events
Future<void> registerPostEvents(int id) async { Future<void> registerPostEvents(int id) async {
if (_registeredPosts.containsKey(id)) if (_registeredPosts.containsKey(id))
_registeredPosts[id]++; _registeredPosts.update(id, (v) => v + 1);
else { else {
_registeredPosts[id] = 1; _registeredPosts[id] = 1;
await ws("\$main/register_post", {"postID": id}); await ws("\$main/register_post", {"postID": id});
@ -232,9 +235,9 @@ class PostsHelper {
Future<void> unregisterPostEvents(int id) async { Future<void> unregisterPostEvents(int id) async {
if (!_registeredPosts.containsKey(id)) return; if (!_registeredPosts.containsKey(id)) return;
_registeredPosts[id]--; _registeredPosts.update(id, (v) => v - 1);
if (_registeredPosts[id] <= 0) { if (_registeredPosts[id]! <= 0) {
_registeredPosts.remove(id); _registeredPosts.remove(id);
await ws("\$main/unregister_post", {"postID": id}); await ws("\$main/unregister_post", {"postID": id});
} }
@ -242,14 +245,14 @@ class PostsHelper {
/// Turn an API entry into a [Post] object /// Turn an API entry into a [Post] object
Post _apiToPost(Map<String, dynamic> map) { Post _apiToPost(Map<String, dynamic> map) {
final postKind = _APIPostsKindsMap[map["kind"]]; final postKind = _APIPostsKindsMap[map["kind"]]!;
// Parse comments // Parse comments
CommentsList comments; CommentsList? comments;
if (map["comments"] != null) { if (map["comments"] != null) {
comments = CommentsList(); comments = CommentsList();
map["comments"] map["comments"]
.forEach((v) => comments.add(CommentsHelper.apiToComment(v))); .forEach((v) => comments!.add(CommentsHelper.apiToComment(v)));
} }
final survey = postKind == PostKind.SURVEY final survey = postKind == PostKind.SURVEY
@ -263,7 +266,7 @@ class PostsHelper {
groupID: map["group_id"], groupID: map["group_id"],
timeSent: map["post_time"], timeSent: map["post_time"],
content: DisplayedString(map["content"]), content: DisplayedString(map["content"]),
visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]], visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]]!,
kind: postKind, kind: postKind,
fileSize: map["file_size"], fileSize: map["file_size"],
fileType: map["file_type"], fileType: map["file_type"],
@ -276,7 +279,7 @@ class PostsHelper {
linkImage: map["link_image"], linkImage: map["link_image"],
likes: map["likes"], likes: map["likes"],
userLike: map["userlike"], userLike: map["userlike"],
access: _APIUserAccessMap[map["user_access"]], access: _APIUserAccessMap[map["user_access"]]!,
comments: comments, comments: comments,
survey: survey); survey: survey);
} }

View File

@ -30,7 +30,7 @@ const _PreferenceKeysName = {
}; };
class PreferencesHelper { class PreferencesHelper {
static PreferencesHelper _instance; static PreferencesHelper? _instance;
static Future<PreferencesHelper> getInstance() async { static Future<PreferencesHelper> getInstance() async {
if (_instance == null) { if (_instance == null) {
@ -38,10 +38,10 @@ class PreferencesHelper {
await _init(); await _init();
} }
return _instance; return _instance!;
} }
static SharedPreferences _sharedPreferences; static late SharedPreferences _sharedPreferences;
PreferencesHelper._(); PreferencesHelper._();
@ -50,7 +50,7 @@ class PreferencesHelper {
} }
/// Set new login tokens /// Set new login tokens
Future<void> setLoginToken(String token) async { Future<void> setLoginToken(String? token) async {
if (token != null) if (token != null)
await setString(PreferencesKeyList.LOGIN_TOKEN, token); await setString(PreferencesKeyList.LOGIN_TOKEN, token);
else else
@ -58,7 +58,7 @@ class PreferencesHelper {
} }
/// Get current [LoginTokens]. Returns null if none or in case of failure /// Get current [LoginTokens]. Returns null if none or in case of failure
String getLoginToken() { String? getLoginToken() {
try { try {
final string = getString(PreferencesKeyList.LOGIN_TOKEN); final string = getString(PreferencesKeyList.LOGIN_TOKEN);
return string; return string;
@ -69,35 +69,35 @@ class PreferencesHelper {
} }
bool containsKey(PreferencesKeyList key) { bool containsKey(PreferencesKeyList key) {
return _sharedPreferences.containsKey(_PreferenceKeysName[key]); return _sharedPreferences.containsKey(_PreferenceKeysName[key]!);
} }
Future<bool> removeKey(PreferencesKeyList key) async { Future<bool> removeKey(PreferencesKeyList key) async {
return await _sharedPreferences.remove(_PreferenceKeysName[key]); return await _sharedPreferences.remove(_PreferenceKeysName[key]!);
} }
Future<bool> setString(PreferencesKeyList key, String value) async { Future<bool> setString(PreferencesKeyList key, String value) async {
return await _sharedPreferences.setString(_PreferenceKeysName[key], value); return await _sharedPreferences.setString(_PreferenceKeysName[key]!, value);
} }
String getString(PreferencesKeyList key) { String? getString(PreferencesKeyList key) {
return _sharedPreferences.getString(_PreferenceKeysName[key]); return _sharedPreferences.getString(_PreferenceKeysName[key]!);
} }
Future<bool> setBool(PreferencesKeyList key, bool value) async { Future<bool> setBool(PreferencesKeyList key, bool value) async {
return await _sharedPreferences.setBool(_PreferenceKeysName[key], value); return await _sharedPreferences.setBool(_PreferenceKeysName[key]!, value);
} }
Future<bool> setInt(PreferencesKeyList key, int value) async { Future<bool> setInt(PreferencesKeyList key, int value) async {
return await _sharedPreferences.setInt(_PreferenceKeysName[key], value); return await _sharedPreferences.setInt(_PreferenceKeysName[key]!, value);
} }
int getInt(PreferencesKeyList key) { int? getInt(PreferencesKeyList key) {
return _sharedPreferences.getInt(_PreferenceKeysName[key]); return _sharedPreferences.getInt(_PreferenceKeysName[key]!);
} }
bool getBool(PreferencesKeyList key, {bool alternative = false}) { bool getBool(PreferencesKeyList key, {bool alternative = false}) {
final v = _sharedPreferences.getBool(_PreferenceKeysName[key]); final v = _sharedPreferences.getBool(_PreferenceKeysName[key]!);
return v == null ? alternative : v; return v == null ? alternative : v;
} }
@ -115,7 +115,7 @@ class PreferencesHelper {
} }
} }
PreferencesHelper preferences() { PreferencesHelper? preferences() {
if (PreferencesHelper._instance == null) if (PreferencesHelper._instance == null)
throw Exception("Try to get preference before their initialization!"); throw Exception("Try to get preference before their initialization!");

View File

@ -21,14 +21,14 @@ const _PushNotificationsAPIMap = {
class PushNotificationsHelper { class PushNotificationsHelper {
/// Get cached status of push notifications /// Get cached status of push notifications
static Future<PushNotificationsStatus> getLocalStatus() async { static Future<PushNotificationsStatus?> getLocalStatus() async {
final pref = await PreferencesHelper.getInstance(); final pref = await PreferencesHelper.getInstance();
if (!pref.containsKey(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS)) if (!pref.containsKey(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS))
return PushNotificationsStatus.UNDEFINED; return PushNotificationsStatus.UNDEFINED;
return _PushNotificationsAPIMap[ return _PushNotificationsAPIMap[
pref.getString(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS)]; pref.getString(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS)!];
} }
/// Refresh local status with information from server /// Refresh local status with information from server
@ -62,8 +62,8 @@ class PushNotificationsHelper {
/// Configure push notifications /// Configure push notifications
static Future<void> configure( static Future<void> configure(
BuildContext context, PushNotificationsStatus newStatus) async { BuildContext context, PushNotificationsStatus? newStatus) async {
String firebaseToken = ""; String? firebaseToken = "";
switch (newStatus) { switch (newStatus) {
case PushNotificationsStatus.DISABLED: case PushNotificationsStatus.DISABLED:
break; break;
@ -90,8 +90,8 @@ class PushNotificationsHelper {
/// Set new push notification status on the server /// Set new push notification status on the server
static Future<void> setNewStatus( static Future<void> setNewStatus(
PushNotificationsStatus newStatus, { PushNotificationsStatus? newStatus, {
String firebaseToken = "", String? firebaseToken = "",
}) async => }) async =>
await APIRequest.withLogin("push_notifications/configure") await APIRequest.withLogin("push_notifications/configure")
.addString( .addString(
@ -104,6 +104,6 @@ class PushNotificationsHelper {
/// Is true if possible if push notifications are configurable /// Is true if possible if push notifications are configurable
static bool get arePushNotificationsAvailable => static bool get arePushNotificationsAvailable =>
srvConfig.notificationsPolicy.hasFirebase || srvConfig!.notificationsPolicy.hasFirebase ||
(isAndroid && srvConfig.notificationsPolicy.hasIndependent); (isAndroid && srvConfig!.notificationsPolicy.hasIndependent);
} }

View File

@ -0,0 +1,38 @@
import 'package:comunic/enums/report_target_type.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/report_target.dart';
import 'package:comunic/models/server_config.dart';
/// Reports Helper
///
/// @author Pierre Hubert
enum ReportResult {
Success,
ErrorAlreadyReported,
Error,
}
class ReportHelper {
/// Send a new report to the server
static Future<ReportResult> sendReport({
required ReportCause cause,
required ReportTarget target,
required String comment,
}) async {
final response = await APIRequest.withLogin("reports/create", args: {
"cause": cause.id,
"target_type": target.type.apiId,
"target_id": target.id.toString(),
"comment": comment
}).exec();
if (response.isOK) return ReportResult.Success;
print("Failed to send report: ${response.content}");
if (response.code == 409) return ReportResult.ErrorAlreadyReported;
return ReportResult.Error;
}
}

View File

@ -13,7 +13,7 @@ class SearchHelper {
/// Search for user. This method returns information about the target users /// Search for user. This method returns information about the target users
/// ///
/// Returns information about the target users or null if an error occurred /// Returns information about the target users or null if an error occurred
Future<UsersList> searchUser(String query) async { Future<UsersList?> searchUser(String query) async {
// Execute the query on the server // Execute the query on the server
final response = await APIRequest( final response = await APIRequest(
uri: "user/search", needLogin: true, args: {"query": query}).exec(); uri: "user/search", needLogin: true, args: {"query": query}).exec();
@ -21,7 +21,7 @@ class SearchHelper {
if (response.code != 200) return null; if (response.code != 200) return null;
return await UsersHelper() return await UsersHelper()
.getUsersInfo(response.getArray().map((f) => cast<int>(f)).toList()); .getUsersInfo(response.getArray()!.map((f) => cast<int>(f)).toList());
} }
/// Perform a global search /// Perform a global search
@ -31,7 +31,7 @@ class SearchHelper {
result.assertOk(); result.assertOk();
return SearchResultsList()..addAll(result.getArray().map((f) { return SearchResultsList()..addAll(result.getArray()!.map((f) {
switch (f["kind"]) { switch (f["kind"]) {
case "user": case "user":
return SearchResult(id: f["id"], kind: SearchResultKind.USER); return SearchResult(id: f["id"], kind: SearchResultKind.USER);

View File

@ -17,7 +17,7 @@ abstract class SerializableElement<T> extends Comparable<T> {
abstract class BaseSerializationHelper<T extends SerializableElement> { abstract class BaseSerializationHelper<T extends SerializableElement> {
/// List cache /// List cache
List<T> _cache; List<T>? _cache;
/// The name of the type of data to serialise /// The name of the type of data to serialise
String get type; String get type;
@ -48,12 +48,15 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
try { try {
final file = await _getFilePath(); final file = await _getFilePath();
if (!await file.exists()) return _cache = []; if (!await file.exists()) {
_cache = [];
return;
}
final List<dynamic> json = jsonDecode(await file.readAsString()); final List<dynamic> json = jsonDecode(await file.readAsString());
_cache = json.cast<Map<String, dynamic>>().map(parse).toList(); _cache = json.cast<Map<String, dynamic>>().map(parse).toList();
_cache.sort(); _cache!.sort();
} catch (e, s) { } catch (e, s) {
logError(e, s); logError(e, s);
print("Failed to read serialized data!"); print("Failed to read serialized data!");
@ -67,8 +70,10 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
try { try {
final file = await _getFilePath(); final file = await _getFilePath();
await file.writeAsString(jsonEncode( await file.writeAsString(jsonEncode(_cache!
_cache.map((e) => e.toJson()).toList().cast<Map<String, dynamic>>())); .map((e) => e.toJson())
.toList()
.cast<Map<String, dynamic>>()));
} catch (e, s) { } catch (e, s) {
print("Failed to write file!"); print("Failed to write file!");
logError(e, s); logError(e, s);
@ -78,7 +83,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
/// Get the current list of elements /// Get the current list of elements
Future<List<T>> getList() async { Future<List<T>> getList() async {
await _loadCache(); await _loadCache();
return List.from(_cache); return List.from(_cache!);
} }
/// Set a new list of conversations /// Set a new list of conversations
@ -90,23 +95,23 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
/// Insert new element /// Insert new element
Future<void> insert(T el) async { Future<void> insert(T el) async {
await _loadCache(); await _loadCache();
_cache.add(el); _cache!.add(el);
_cache.sort(); _cache!.sort();
await _saveCache(); await _saveCache();
} }
/// Insert new element /// Insert new element
Future<void> insertMany(List<T> els) async { Future<void> insertMany(List<T> els) async {
await _loadCache(); await _loadCache();
_cache.addAll(els); _cache!.addAll(els);
_cache.sort(); _cache!.sort();
await _saveCache(); await _saveCache();
} }
/// Check if any entry in the last match the predicate /// Check if any entry in the last match the predicate
Future<bool> any(bool isContained(T t)) async { Future<bool> any(bool isContained(T t)) async {
await _loadCache(); await _loadCache();
return _cache.any((element) => isContained(element)); return _cache!.any((element) => isContained(element));
} }
Future<bool> has(T el) => any((t) => t == el); Future<bool> has(T el) => any((t) => t == el);
@ -114,7 +119,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
/// Check if any entry in the last match the predicate /// Check if any entry in the last match the predicate
Future<T> first(bool filter(T t)) async { Future<T> first(bool filter(T t)) async {
await _loadCache(); await _loadCache();
return _cache.firstWhere((element) => filter(element)); return _cache!.firstWhere((element) => filter(element));
} }
/// Replace an element with another one /// Replace an element with another one
@ -122,10 +127,10 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
await _loadCache(); await _loadCache();
// Insert or replace the element // Insert or replace the element
_cache = _cache.where((element) => !isToReplace(element)).toList(); _cache = _cache!.where((element) => !isToReplace(element)).toList();
_cache.add(newEl); _cache!.add(newEl);
_cache.sort(); _cache!.sort();
await _saveCache(); await _saveCache();
} }
@ -133,8 +138,8 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
Future<void> insertOrReplaceElements(List<T> list) async { Future<void> insertOrReplaceElements(List<T> list) async {
await _loadCache(); await _loadCache();
_cache.removeWhere((element) => list.any((newEl) => element == newEl)); _cache!.removeWhere((element) => list.any((newEl) => element == newEl));
_cache.addAll(list); _cache!.addAll(list);
await _saveCache(); await _saveCache();
} }
@ -142,7 +147,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
/// Remove elements /// Remove elements
Future<void> removeElement(bool isToRemove(T t)) async { Future<void> removeElement(bool isToRemove(T t)) async {
await _loadCache(); await _loadCache();
_cache.removeWhere((element) => isToRemove(element)); _cache!.removeWhere((element) => isToRemove(element));
await _saveCache(); await _saveCache();
} }

View File

@ -8,23 +8,22 @@ import 'package:comunic/models/conversation_message.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
HashMap<int, ConversationsMessagesSerializationHelper> _instances; HashMap<int?, ConversationsMessagesSerializationHelper>? _instances;
class ConversationsMessagesSerializationHelper class ConversationsMessagesSerializationHelper
extends BaseSerializationHelper<ConversationMessage> { extends BaseSerializationHelper<ConversationMessage> {
final int convID; final int convID;
ConversationsMessagesSerializationHelper._(int convID) ConversationsMessagesSerializationHelper._(int convID)
: convID = convID, : convID = convID;
assert(convID != null);
factory ConversationsMessagesSerializationHelper(int convID) { factory ConversationsMessagesSerializationHelper(int? convID) {
if (_instances == null) _instances = HashMap(); if (_instances == null) _instances = HashMap();
if (!_instances.containsKey(convID)) if (!_instances!.containsKey(convID))
_instances[convID] = ConversationsMessagesSerializationHelper._(convID); _instances![convID] = ConversationsMessagesSerializationHelper._(convID!);
return _instances[convID]; return _instances![convID]!;
} }
@override @override

View File

@ -28,5 +28,5 @@ class ConversationsSerializationHelper
ConversationsList()..addAll(await super.getList()); ConversationsList()..addAll(await super.getList());
/// Get a conversation /// Get a conversation
Future<Conversation> get(int id) => first((t) => t.id == id); Future<Conversation> get(int? id) => first((t) => t.id == id);
} }

View File

@ -5,7 +5,7 @@ import 'package:comunic/models/user.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
UsersListSerialisationHelper _singleton; UsersListSerialisationHelper? _singleton;
class UsersListSerialisationHelper extends BaseSerializationHelper<User> { class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
UsersListSerialisationHelper._(); UsersListSerialisationHelper._();
@ -13,7 +13,7 @@ class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
factory UsersListSerialisationHelper() { factory UsersListSerialisationHelper() {
if (_singleton == null) _singleton = UsersListSerialisationHelper._(); if (_singleton == null) _singleton = UsersListSerialisationHelper._();
return _singleton; return _singleton!;
} }
@override @override
@ -23,6 +23,6 @@ class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
User parse(Map<String, dynamic> m) => User.fromJson(m); User parse(Map<String, dynamic> m) => User.fromJson(m);
/// Remove a user by its ID /// Remove a user by its ID
Future<void> removeUserByID(int userID) => Future<void> removeUserByID(int? userID) =>
removeElement((t) => t.id == userID); removeElement((t) => t.id == userID);
} }

View File

@ -7,7 +7,7 @@ import 'package:version/version.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class ServerConfigurationHelper { class ServerConfigurationHelper {
static ServerConfig _config; static ServerConfig? _config;
/// Make sure the configuration has been correctly loaded /// Make sure the configuration has been correctly loaded
static Future<void> ensureLoaded() async { static Future<void> ensureLoaded() async {
@ -17,11 +17,13 @@ class ServerConfigurationHelper {
(await APIRequest.withoutLogin("server/config").execWithThrow()) (await APIRequest.withoutLogin("server/config").execWithThrow())
.getObject(); .getObject();
final banner = response["banner"];
final pushNotificationsPolicy = response["push_notifications"]; final pushNotificationsPolicy = response["push_notifications"];
final passwordPolicy = response["password_policy"]; final passwordPolicy = response["password_policy"];
final dataConservationPolicy = response["data_conservation_policy"]; final dataConservationPolicy = response["data_conservation_policy"];
final conversationsPolicy = response["conversations_policy"]; final conversationsPolicy = response["conversations_policy"];
final accountInformationPolicy = response["account_info_policy"]; final accountInformationPolicy = response["account_info_policy"];
final reportPolicy = response["report_policy"];
_config = ServerConfig( _config = ServerConfig(
minSupportedMobileVersion: minSupportedMobileVersion:
@ -31,6 +33,15 @@ class ServerConfigurationHelper {
contactEmail: response["contact_email"], contactEmail: response["contact_email"],
playStoreURL: response["play_store_url"], playStoreURL: response["play_store_url"],
androidDirectDownloadURL: response["android_direct_download_url"], androidDirectDownloadURL: response["android_direct_download_url"],
banner: banner == null
? null
: Banner(
enabled: banner["enabled"],
expire: banner["expire"],
nature: BannerNatureExt.fromStr(banner["nature"]),
message: Map<String, dynamic>.from(banner["message"])
.map((key, value) => MapEntry(key, value.toString())),
link: banner["link"]),
notificationsPolicy: NotificationsPolicy( notificationsPolicy: NotificationsPolicy(
hasFirebase: pushNotificationsPolicy["has_firebase"], hasFirebase: pushNotificationsPolicy["has_firebase"],
hasIndependent: pushNotificationsPolicy["has_independent"], hasIndependent: pushNotificationsPolicy["has_independent"],
@ -70,7 +81,8 @@ class ServerConfigurationHelper {
writingEventInterval: conversationsPolicy["writing_event_interval"], writingEventInterval: conversationsPolicy["writing_event_interval"],
writingEventLifetime: conversationsPolicy["writing_event_lifetime"], writingEventLifetime: conversationsPolicy["writing_event_lifetime"],
maxMessageImageWidth: conversationsPolicy["max_message_image_width"], maxMessageImageWidth: conversationsPolicy["max_message_image_width"],
maxMessageImageHeight: conversationsPolicy["max_message_image_height"], maxMessageImageHeight:
conversationsPolicy["max_message_image_height"],
maxThumbnailWidth: conversationsPolicy["max_thumbnail_width"], maxThumbnailWidth: conversationsPolicy["max_thumbnail_width"],
maxThumbnailHeight: conversationsPolicy["max_thumbnail_height"], maxThumbnailHeight: conversationsPolicy["max_thumbnail_height"],
maxLogoWidth: conversationsPolicy["max_logo_width"], maxLogoWidth: conversationsPolicy["max_logo_width"],
@ -83,11 +95,22 @@ class ServerConfigurationHelper {
maxLastNameLength: accountInformationPolicy["max_last_name_length"], maxLastNameLength: accountInformationPolicy["max_last_name_length"],
maxLocationLength: accountInformationPolicy["max_location_length"], maxLocationLength: accountInformationPolicy["max_location_length"],
), ),
); reportPolicy: reportPolicy == null
? null
: ReportPolicy(
causes: List.from(reportPolicy["causes"]
.map((cause) => ReportCause(
id: cause["id"],
label: new Map<String, String>.from(cause["label"])))
.toList()),
maxCommentLength: reportPolicy["max_comment_length"],
canUserReportHisOwnContent:
reportPolicy["can_user_report_his_own_content"],
));
} }
/// Get current server configuration, throwing if it is not loaded yet /// Get current server configuration, throwing if it is not loaded yet
static ServerConfig get config { static ServerConfig? get config {
if (_config == null) if (_config == null)
throw Exception( throw Exception(
"Trying to access server configuration but it is not loaded yet!"); "Trying to access server configuration but it is not loaded yet!");
@ -97,4 +120,6 @@ class ServerConfigurationHelper {
} }
/// Shortcut for server configuration /// Shortcut for server configuration
ServerConfig get srvConfig => ServerConfigurationHelper.config; ServerConfig? get srvConfig => ServerConfigurationHelper.config;
bool get showBanner => srvConfig!.banner != null && srvConfig!.banner!.visible;

View File

@ -94,7 +94,7 @@ class SettingsHelper {
hasImage: response["has_image"], hasImage: response["has_image"],
imageURL: response["image_url"], imageURL: response["image_url"],
visibility: visibility:
_APIAccountImageVisibilityAPILevels[response["visibility"]]); _APIAccountImageVisibilityAPILevels[response["visibility"]]!);
} }
/// Upload a new account image /// Upload a new account image
@ -143,7 +143,7 @@ class SettingsHelper {
/// Delete a custom emoji /// Delete a custom emoji
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> deleteCustomEmoji(int emojiID) async => static Future<void> deleteCustomEmoji(int? emojiID) async =>
(await APIRequest(uri: "settings/delete_custom_emoji", needLogin: true) (await APIRequest(uri: "settings/delete_custom_emoji", needLogin: true)
.addInt("emojiID", emojiID) .addInt("emojiID", emojiID)
.exec()) .exec())
@ -223,7 +223,7 @@ class SettingsHelper {
/// ///
/// Throws in case of failure /// Throws in case of failure
static Future<void> setDataConservationPolicy( static Future<void> setDataConservationPolicy(
String password, DataConservationPolicySettings newSettings) async { String? password, DataConservationPolicySettings newSettings) async {
await APIRequest( await APIRequest(
uri: "settings/set_data_conservation_policy", needLogin: true) uri: "settings/set_data_conservation_policy", needLogin: true)
.addString("password", password) .addString("password", password)

View File

@ -1,7 +1,6 @@
import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/survey.dart'; import 'package:comunic/models/survey.dart';
import 'package:comunic/models/survey_choice.dart'; import 'package:comunic/models/survey_choice.dart';
import 'package:meta/meta.dart';
/// Survey helper /// Survey helper
/// ///
@ -26,9 +25,7 @@ class SurveyHelper {
/// Send the response of a user to a survey /// Send the response of a user to a survey
Future<bool> respondToSurvey( Future<bool> respondToSurvey(
{@required Survey survey, @required SurveyChoice choice}) async { {required Survey survey, required SurveyChoice choice}) async {
assert(survey != null);
assert(choice != null);
return (await APIRequest( return (await APIRequest(
uri: "surveys/send_response", uri: "surveys/send_response",

View File

@ -23,7 +23,7 @@ enum GetUserAdvancedInformationErrorCause {
class GetUserAdvancedUserError extends Error { class GetUserAdvancedUserError extends Error {
final GetUserAdvancedInformationErrorCause cause; final GetUserAdvancedInformationErrorCause cause;
GetUserAdvancedUserError(this.cause) : assert(cause != null); GetUserAdvancedUserError(this.cause);
} }
class UsersHelper { class UsersHelper {
@ -31,7 +31,7 @@ class UsersHelper {
/// ///
/// Return the list of users information in case of success, null in case of /// Return the list of users information in case of success, null in case of
/// failure /// failure
Future<UsersList> _downloadInfo(List<int> users) async { Future<UsersList?> _downloadInfo(List<int?> users) async {
// Execute the request // Execute the request
final response = await APIRequest( final response = await APIRequest(
uri: "user/getInfoMultiple", uri: "user/getInfoMultiple",
@ -69,7 +69,7 @@ class UsersHelper {
/// Get users information from a given [Set]. Throws an exception in case /// Get users information from a given [Set]. Throws an exception in case
/// of failure /// of failure
Future<UsersList> getListWithThrow(Set<int> users, Future<UsersList> getListWithThrow(Set<int?> users,
{bool forceDownload = false}) async { {bool forceDownload = false}) async {
final list = final list =
await getUsersInfo(users.toList(), forceDownload: forceDownload); await getUsersInfo(users.toList(), forceDownload: forceDownload);
@ -82,16 +82,16 @@ class UsersHelper {
} }
/// Get information about a single user. Throws in case of failure /// Get information about a single user. Throws in case of failure
Future<User> getSingleWithThrow(int user, Future<User> getSingleWithThrow(int? user,
{bool forceDownload = false}) async { {bool forceDownload = false}) async {
return (await getListWithThrow(Set<int>()..add(user), return (await getListWithThrow(Set<int?>()..add(user),
forceDownload: forceDownload))[0]; forceDownload: forceDownload))[0];
} }
/// Get users information from a given [Set] /// Get users information from a given [Set]
/// ///
/// Throws in case of failure /// Throws in case of failure
Future<UsersList> getList(Set<int> users, Future<UsersList> getList(Set<int?> users,
{bool forceDownload = false}) async { {bool forceDownload = false}) async {
final list = await getUsersInfo(users.toList()); final list = await getUsersInfo(users.toList());
@ -104,13 +104,13 @@ class UsersHelper {
/// ///
/// If [forceDownload] is set to true, the data will always be retrieved from /// If [forceDownload] is set to true, the data will always be retrieved from
/// the server, otherwise cached data will be used if available /// the server, otherwise cached data will be used if available
Future<UsersList> getUsersInfo(List<int> users, Future<UsersList?> getUsersInfo(List<int?> users,
{bool forceDownload = false}) async { {bool forceDownload = false}) async {
List<int> toDownload = []; List<int?> toDownload = [];
UsersList list = UsersList(); UsersList list = UsersList();
// Check cache // Check cache
for (int userID in users) { for (int? userID in users) {
if (!forceDownload && if (!forceDownload &&
await UsersListSerialisationHelper().any((u) => u.id == userID)) await UsersListSerialisationHelper().any((u) => u.id == userID))
list.add( list.add(

View File

@ -1,5 +1,5 @@
import 'package:comunic/utils/flutter_utils.dart'; import 'package:comunic/utils/flutter_utils.dart';
import 'package:package_info/package_info.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:version/version.dart'; import 'package:version/version.dart';
/// Application version helper /// Application version helper
@ -7,15 +7,15 @@ import 'package:version/version.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class VersionHelper { class VersionHelper {
static PackageInfo _info; static PackageInfo? _info;
static Future<void> ensureLoaded() async { static Future<void> ensureLoaded() async {
if (!isWeb) _info = await PackageInfo.fromPlatform(); if (!isWeb) _info = await PackageInfo.fromPlatform();
} }
/// Get current version information /// Get current version information
static PackageInfo get info => _info; static PackageInfo? get info => _info;
/// Get current application version, in parsed format /// Get current application version, in parsed format
static Version get version => Version.parse(info.version); static Version get version => Version.parse(info!.version);
} }

View File

@ -1,5 +1,4 @@
import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_request.dart';
import 'package:flutter/material.dart';
/// Virtual directory helper /// Virtual directory helper
/// ///
@ -9,12 +8,12 @@ enum VirtualDirectoryType { USER, GROUP, NONE }
class VirtualDirectoryResult { class VirtualDirectoryResult {
final VirtualDirectoryType type; final VirtualDirectoryType type;
final int id; final int? id;
const VirtualDirectoryResult({ const VirtualDirectoryResult({
@required this.type, required this.type,
this.id, this.id,
}) : assert(type != null); });
} }
class VirtualDirectoryHelper { class VirtualDirectoryHelper {
@ -43,7 +42,6 @@ class VirtualDirectoryHelper {
default: default:
throw Exception("Unsupported virtual directory kind: $kind"); throw Exception("Unsupported virtual directory kind: $kind");
} }
break;
default: default:
throw new Exception("Could not get virtual directory!"); throw new Exception("Could not get virtual directory!");

View File

@ -15,7 +15,7 @@ class WebAppHelper {
static Future<MembershipList> getMemberships() async { static Future<MembershipList> getMemberships() async {
final response = final response =
(await APIRequest.withLogin("webApp/getMemberships").execWithThrow()) (await APIRequest.withLogin("webApp/getMemberships").execWithThrow())
.getArray(); .getArray()!;
return MembershipList() return MembershipList()
..addAll(response ..addAll(response
@ -26,7 +26,7 @@ class WebAppHelper {
} }
/// Turn an API entry into a membership entry /// Turn an API entry into a membership entry
static Membership _apiToMembership(Map<String, dynamic> entry) { static Membership? _apiToMembership(Map<String, dynamic> entry) {
switch (entry["type"]) { switch (entry["type"]) {
case "conversation": case "conversation":
return Membership.conversation( return Membership.conversation(

View File

@ -15,7 +15,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class WebSocketHelper { class WebSocketHelper {
static WebSocketChannel _ws; static WebSocketChannel? _ws;
static int _counter = 0; static int _counter = 0;
@ -23,11 +23,11 @@ class WebSocketHelper {
/// Check out whether we are currently connected to WebSocket or not /// Check out whether we are currently connected to WebSocket or not
static bool isConnected() { static bool isConnected() {
return _ws != null && _ws.closeCode == null; return _ws != null && _ws!.closeCode == null;
} }
/// Get WebSocket access token /// Get WebSocket access token
static Future<String> _getWsToken() async => static Future<String?> _getWsToken() async =>
(await APIRequest(uri: "ws/token", needLogin: true).exec()) (await APIRequest(uri: "ws/token", needLogin: true).exec())
.assertOk() .assertOk()
.getObject()["token"]; .getObject()["token"];
@ -47,7 +47,7 @@ class WebSocketHelper {
// Connect // Connect
_ws = WebSocketChannel.connect(wsURI); _ws = WebSocketChannel.connect(wsURI);
_ws.stream.listen( _ws!.stream.listen(
// When we got data // When we got data
(data) { (data) {
print("WS New data: $data"); print("WS New data: $data");
@ -75,7 +75,7 @@ class WebSocketHelper {
/// Close current WebSocket (if any) /// Close current WebSocket (if any)
static close() { static close() {
if (isConnected()) _ws.sink.close(); if (isConnected()) _ws!.sink.close();
} }
/// Send a new message /// Send a new message
@ -93,7 +93,7 @@ class WebSocketHelper {
print("WS Send message: $msg"); print("WS Send message: $msg");
_ws.sink.add(msg); _ws!.sink.add(msg);
_requests[id] = completer; _requests[id] = completer;
return completer.future; return completer.future;
@ -240,11 +240,11 @@ class WebSocketHelper {
// Handles errors // Handles errors
if (msg.title != "success") { if (msg.title != "success") {
completer.completeError(Exception("Could not process request!")); completer!.completeError(Exception("Could not process request!"));
return; return;
} }
completer.complete(msg.data); completer!.complete(msg.data);
} }
} }

View File

@ -16,4 +16,7 @@ class AbstractList<E> extends ListBase<E> {
@override @override
void operator []=(int index, E value) => _list[index] = value; void operator []=(int index, E value) => _list[index] = value;
@override
void add(E element) => _list.add(element);
} }

View File

@ -11,7 +11,7 @@ class BaseSet<T> extends SetBase<T> {
bool add(T value) => _set.add(value); bool add(T value) => _set.add(value);
@override @override
bool contains(Object element) => _set.contains(element); bool contains(Object? element) => _set.contains(element);
@override @override
Iterator<T> get iterator => _set.iterator; Iterator<T> get iterator => _set.iterator;
@ -20,10 +20,10 @@ class BaseSet<T> extends SetBase<T> {
int get length => _set.length; int get length => _set.length;
@override @override
T lookup(Object element) => _set.lookup(element); T? lookup(Object? element) => _set.lookup(element);
@override @override
bool remove(Object value) => _set.remove(value); bool remove(Object? value) => _set.remove(value);
@override @override
Set<T> toSet() => _set.toSet(); Set<T> toSet() => _set.toSet();

View File

@ -10,10 +10,10 @@ class CallMembersList extends AbstractList<CallMember> {
Set<int> get usersID => this.map((f) => f.userID).toSet(); Set<int> get usersID => this.map((f) => f.userID).toSet();
/// Remove a specific member from this list /// Remove a specific member from this list
void removeUser(int userID) => this.removeWhere((f) => f.userID == userID); void removeUser(int? userID) => this.removeWhere((f) => f.userID == userID);
/// Get the connection of a specific user /// Get the connection of a specific user
CallMember getUser(int userID) => this.firstWhere((f) => f.userID == userID); CallMember getUser(int? userID) => this.firstWhere((f) => f.userID == userID);
/// Extract ready peers from this list /// Extract ready peers from this list
CallMembersList get readyPeers => CallMembersList get readyPeers =>

View File

@ -1,5 +1,4 @@
import 'dart:collection'; import 'package:comunic/lists/abstract_list.dart';
import 'package:comunic/models/comment.dart'; import 'package:comunic/models/comment.dart';
/// Comments list /// Comments list
@ -8,19 +7,7 @@ import 'package:comunic/models/comment.dart';
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class CommentsList extends ListBase<Comment> { class CommentsList extends AbstractList<Comment> {
List<Comment> _list = [];
int get length => _list.length;
set length(int l) => _list.length = l;
@override
Comment operator [](int index) => _list[index];
@override
void operator []=(int index, Comment value) => _list[index] = value;
/// Get the list of users in this comments, as a set /// Get the list of users in this comments, as a set
Set<int> get usersID => map((f) => f.userID).toSet(); Set<int> get usersID => map((f) => f.userID).toSet();
} }

View File

@ -1,31 +1,14 @@
import 'dart:collection'; import 'package:comunic/lists/abstract_list.dart';
import 'package:comunic/models/conversation_message.dart'; import 'package:comunic/models/conversation_message.dart';
/// Conversations messages list /// Conversations messages list
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class ConversationMessagesList extends ListBase<ConversationMessage> { class ConversationMessagesList extends AbstractList<ConversationMessage> {
final List<ConversationMessage> _list = [];
set length(int v) => _list.length = v;
int get length => _list.length;
@override
ConversationMessage operator [](int index) {
return _list[index];
}
@override
void operator []=(int index, ConversationMessage value) {
_list[index] = value;
}
/// Get the list of the users ID who own a message in this list /// Get the list of the users ID who own a message in this list
Set<int> getUsersID() { Set<int?> getUsersID() {
final Set<int> users = Set(); final Set<int?> users = Set();
for (ConversationMessage message in this) users.addAll(message.usersID); for (ConversationMessage message in this) users.addAll(message.usersID);
@ -33,18 +16,18 @@ class ConversationMessagesList extends ListBase<ConversationMessage> {
} }
/// Get the ID of the last message present in this list /// Get the ID of the last message present in this list
int get lastMessageID { int? get lastMessageID {
int lastMessageID = 0; int? lastMessageID = 0;
for (ConversationMessage message in this) for (ConversationMessage message in this)
if (message.id > lastMessageID) lastMessageID = message.id; if (message.id! > lastMessageID!) lastMessageID = message.id;
return lastMessageID; return lastMessageID;
} }
/// Get the ID of the first message present in this list /// Get the ID of the first message present in this list
int get firstMessageID { int? get firstMessageID {
int firstMessageID = this[0].id; int? firstMessageID = this[0].id;
for (ConversationMessage message in this) for (ConversationMessage message in this)
if (message.id < firstMessageID) firstMessageID = message.id; if (message.id! < firstMessageID!) firstMessageID = message.id;
return firstMessageID; return firstMessageID;
} }
@ -55,5 +38,5 @@ class ConversationMessagesList extends ListBase<ConversationMessage> {
} }
/// Remove a message from this list /// Remove a message from this list
void removeMsg(int id) => removeWhere((f) => f.id == id); void removeMsg(int? id) => removeWhere((f) => f.id == id);
} }

View File

@ -22,12 +22,15 @@ class ConversationsList extends ListBase<Conversation> {
/// Get the entire lists of users ID in this list /// Get the entire lists of users ID in this list
Set<int> get allUsersID { Set<int> get allUsersID {
final Set<int> list = Set(); final Set<int> list = Set();
forEach((c) => c.members.forEach((member) => list.add(member.userID))); forEach((c) => c.members!.forEach((member) => list.add(member.userID)));
return list; return list;
} }
/// Get the entire lists of groups ID in this list /// Get the entire lists of groups ID in this list
Set<int> get allGroupsID => where((element) => element.isGroupConversation) Set<int?> get allGroupsID => where((element) => element.isGroupConversation)
.map((e) => e.groupID) .map((e) => e.groupID)
.toSet(); .toSet();
@override
void add(Conversation element) => _list.add(element);
} }

View File

@ -7,8 +7,7 @@ import 'package:comunic/models/custom_emoji.dart';
class CustomEmojiesList extends AbstractList<CustomEmoji> { class CustomEmojiesList extends AbstractList<CustomEmoji> {
/// Check if an emoji, identified by its shortcut, is present in this list /// Check if an emoji, identified by its shortcut, is present in this list
bool hasShortcut(String shortcut) => bool hasShortcut(String shortcut) => any((f) => f.shortcut == shortcut);
firstWhere((f) => f.shortcut == shortcut, orElse: () => null) != null;
/// Serialize this list /// Serialize this list
List<Map<String, dynamic>> toSerializableList() => List<Map<String, dynamic>> toSerializableList() =>

View File

@ -7,7 +7,7 @@ import 'package:comunic/models/forez_presence.dart';
class PresenceSet extends BaseSet<Presence> { class PresenceSet extends BaseSet<Presence> {
/// Get the presence of a specific user /// Get the presence of a specific user
PresenceSet getForUser(int userID) => PresenceSet getForUser(int? userID) =>
PresenceSet()..addAll(where((element) => element.userID == userID)); PresenceSet()..addAll(where((element) => element.userID == userID));
bool containsDate(DateTime dt) => any( bool containsDate(DateTime dt) => any(
@ -24,11 +24,11 @@ class PresenceSet extends BaseSet<Presence> {
element.day == dt.day, element.day == dt.day,
); );
void toggleDate(DateTime dt, int userID) { void toggleDate(DateTime dt, int? userID) {
if (containsDate(dt)) if (containsDate(dt))
removeDate(dt); removeDate(dt);
else else
add(Presence.fromDateTime(dt, userID)); add(Presence.fromDateTime(dt, userID!));
} }
int countAtDate(DateTime dt) => where( int countAtDate(DateTime dt) => where(

View File

@ -1,24 +1,11 @@
import 'dart:collection'; import 'package:comunic/lists/abstract_list.dart';
import 'package:comunic/models/friend.dart'; import 'package:comunic/models/friend.dart';
/// List of friends of the user /// List of friends of the user
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class FriendsList extends ListBase<Friend> { class FriendsList extends AbstractList<Friend> {
List<Friend> _list = [];
int get length => _list.length;
set length(int length) => _list.length = length;
@override
Friend operator [](int index) => _list[index];
@override
void operator []=(int index, Friend value) => _list[index] = value;
/// Get the ID of all the friends of the current user /// Get the ID of all the friends of the current user
List<int> get usersId => map((f) => f.id).toList(); List<int> get usersId => map((f) => f.id).toList();
} }

View File

@ -6,23 +6,23 @@ import 'package:comunic/models/group.dart';
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class GroupsList extends MapBase<int, Group> { class GroupsList extends MapBase<int?, Group> {
final Map<int, Group> _groups = Map(); final Map<int?, Group?> _groups = Map();
@override @override
Group operator [](Object key) => _groups[key]; Group? operator [](Object? key) => _groups[key];
@override @override
void operator []=(int key, Group value) => _groups[key] = value; void operator []=(int? key, Group? value) => _groups[key] = value;
@override @override
void clear() => _groups.clear(); void clear() => _groups.clear();
@override @override
Iterable<int> get keys => _groups.keys; Iterable<int?> get keys => _groups.keys;
@override @override
Group remove(Object key) => _groups.remove(key); Group? remove(Object? key) => _groups.remove(key);
Group getGroup(int id) => this[id]; Group? getGroup(int? id) => this[id];
} }

View File

@ -5,20 +5,20 @@ import 'package:comunic/models/membership.dart';
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
class MembershipList extends AbstractList<Membership> { class MembershipList extends AbstractList<Membership?> {
/// Get the IDs of all the users included in some way in this list /// Get the IDs of all the users included in some way in this list
Set<int> get usersId { Set<int?> get usersId {
final s = Set<int>(); final s = Set<int?>();
forEach((m) { forEach((m) {
switch (m.type) { switch (m!.type) {
case MembershipType.FRIEND: case MembershipType.FRIEND:
s.add(m.friend.id); s.add(m.friend!.id);
break; break;
case MembershipType.GROUP: case MembershipType.GROUP:
break; break;
case MembershipType.CONVERSATION: case MembershipType.CONVERSATION:
s.addAll(m.conversation.membersID); s.addAll(m.conversation!.membersID);
break; break;
} }
}); });
@ -27,16 +27,16 @@ class MembershipList extends AbstractList<Membership> {
} }
/// Get the ID of the groups included in this list /// Get the ID of the groups included in this list
Set<int> get groupsId => where((f) => f.type == MembershipType.GROUP) Set<int?> get groupsId => where((f) => f!.type == MembershipType.GROUP)
.map((f) => f.groupID) .map((f) => f!.groupID)
.toSet(); .toSet();
/// Remove a friend membership from the list /// Remove a friend membership from the list
void removeFriend(int friendID) => remove(firstWhere( void removeFriend(int friendID) => remove(firstWhere(
(f) => f.type == MembershipType.FRIEND && f.friend.id == friendID)); (f) => f!.type == MembershipType.FRIEND && f.friend!.id == friendID));
/// Get the list of conversations of a group /// Get the list of conversations of a group
Set<Membership> getGroupConversations(int groupID) => where((element) => Set<Membership?> getGroupConversations(int groupID) => where((element) =>
element.type == MembershipType.CONVERSATION && element!.type == MembershipType.CONVERSATION &&
element.conversation.groupID == groupID).toSet(); element.conversation!.groupID == groupID).toSet();
} }

View File

@ -8,8 +8,8 @@ import 'package:comunic/models/notification.dart';
class NotificationsList extends AbstractList<Notification> { class NotificationsList extends AbstractList<Notification> {
/// Get the ID of all the users related to the notifications /// Get the ID of all the users related to the notifications
/// included in the list /// included in the list
Set<int> get usersIds { Set<int?> get usersIds {
final list = Set<int>(); final list = Set<int?>();
forEach((n) { forEach((n) {
list.add(n.fromUser); list.add(n.fromUser);
@ -28,8 +28,8 @@ class NotificationsList extends AbstractList<Notification> {
/// Get the ID of all the groups related to the notifications /// Get the ID of all the groups related to the notifications
/// included in the list /// included in the list
Set<int> get groupsIds { Set<int?> get groupsIds {
final list = Set<int>(); final list = Set<int?>();
forEach((n) { forEach((n) {
if (n.onElemType == NotificationElementType.GROUP_PAGE) if (n.onElemType == NotificationElementType.GROUP_PAGE)

View File

@ -1,5 +1,4 @@
import 'dart:collection'; import 'package:comunic/lists/abstract_list.dart';
import 'package:comunic/models/post.dart'; import 'package:comunic/models/post.dart';
/// Posts List /// Posts List
@ -8,37 +7,25 @@ import 'package:comunic/models/post.dart';
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class PostsList extends ListBase<Post> { class PostsList extends AbstractList<Post> {
List<Post> _list = [];
int get length => _list.length;
set length(int l) => _list.length = l;
@override
Post operator [](int index) => _list[index];
@override
void operator []=(int index, Post value) => _list[index] = value;
// Get the list of users ID in this set // Get the list of users ID in this set
Set<int> get usersID { Set<int?> get usersID {
Set<int> set = Set(); Set<int?> set = Set();
forEach((p) { forEach((p) {
set.add(p.userID); set.add(p.userID);
if (p.userPageID != null && p.userPageID > 0) set.add(p.userPageID); if (p.userPageID != null && p.userPageID! > 0) set.add(p.userPageID);
if (p.comments != null) set.addAll(p.comments.usersID); if (p.comments != null) set.addAll(p.comments!.usersID);
}); });
return set; return set;
} }
/// Get the list of groups in this list of posts /// Get the list of groups in this list of posts
Set<int> get groupsID { Set<int?> get groupsID {
Set<int> set = Set(); Set<int?> set = Set();
forEach((p) { forEach((p) {
if (p.isGroupPost) set.add(p.groupID); if (p.isGroupPost) set.add(p.groupID);
@ -49,8 +36,7 @@ class PostsList extends ListBase<Post> {
/// Get the ID of the oldest post of this list. Returns 0 if the list is empty /// Get the ID of the oldest post of this list. Returns 0 if the list is empty
int get oldestID { int get oldestID {
if(isEmpty) if (isEmpty) return 0;
return 0;
return this.elementAt(length - 1).id; return this.elementAt(length - 1).id;
} }

View File

@ -7,8 +7,8 @@ import 'package:comunic/models/unread_conversation.dart';
class UnreadConversationsList extends AbstractList<UnreadConversation> { class UnreadConversationsList extends AbstractList<UnreadConversation> {
/// Get the ID of the users included in this list /// Get the ID of the users included in this list
Set<int> get usersID { Set<int?> get usersID {
final set = Set<int>(); final set = Set<int?>();
forEach((element) { forEach((element) {
set.addAll(element.conv.membersID); set.addAll(element.conv.membersID);
set.addAll(element.message.usersID); set.addAll(element.message.usersID);
@ -17,8 +17,8 @@ class UnreadConversationsList extends AbstractList<UnreadConversation> {
} }
/// Get the ID of the groups references ind this list /// Get the ID of the groups references ind this list
Set<int> get groupsID { Set<int?> get groupsID {
final set = Set<int>(); final set = Set<int?>();
forEach((element) { forEach((element) {
if (element.conv.isGroupConversation) set.add(element.conv.groupID); if (element.conv.isGroupConversation) set.add(element.conv.groupID);
}); });

View File

@ -24,7 +24,7 @@ class UsersList extends ListBase<User> {
} }
/// Find a user with a specific ID /// Find a user with a specific ID
User getUser(int userID) { User getUser(int? userID) {
for (int i = 0; i < this.length; i++) for (int i = 0; i < this.length; i++)
if (this[i].id == userID) return this[i]; if (this[i].id == userID) return this[i];
@ -32,8 +32,11 @@ class UsersList extends ListBase<User> {
} }
/// Check if the user is included in this list or not /// Check if the user is included in this list or not
bool hasUser(int userID) => any((f) => f.id == userID); bool hasUser(int? userID) => any((f) => f.id == userID);
/// Get the list of users ID present in this list /// Get the list of users ID present in this list
List<int> get usersID => List.generate(length, (i) => this[i].id); List<int?> get usersID => List.generate(length, (i) => this[i].id);
@override
void add(User element) => _list.add(element);
} }

View File

@ -7,8 +7,9 @@ import 'package:comunic/models/config.dart';
import 'package:comunic/ui/widgets/init_widget.dart'; import 'package:comunic/ui/widgets/init_widget.dart';
import 'package:comunic/utils/flutter_utils.dart'; import 'package:comunic/utils/flutter_utils.dart';
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:connectivity/connectivity.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// Main file of the application /// Main file of the application
/// ///
@ -43,10 +44,9 @@ class ComunicApplication extends StatefulWidget {
final PreferencesHelper preferences; final PreferencesHelper preferences;
const ComunicApplication({ const ComunicApplication({
Key key, Key? key,
@required this.preferences, required this.preferences,
}) : assert(preferences != null), }) : super(key: key);
super(key: key);
@override @override
ComunicApplicationState createState() => ComunicApplicationState(); ComunicApplicationState createState() => ComunicApplicationState();
@ -68,7 +68,8 @@ class ComunicApplicationState extends State<ComunicApplication> {
primaryColor: config().primaryColor, primaryColor: config().primaryColor,
primaryColorDark: config().primaryColorDark, primaryColorDark: config().primaryColorDark,
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
brightness: Brightness.dark, backgroundColor: config().primaryColor,
systemOverlayStyle: SystemUiOverlayStyle.light,
)), )),
showPerformanceOverlay: prefs.showPerformancesOverlay, showPerformanceOverlay: prefs.showPerformancesOverlay,
); );

View File

@ -11,7 +11,7 @@ import 'package:comunic/utils/flutter_utils.dart';
/// Fix HTTPS issue /// Fix HTTPS issue
class MyHttpOverride extends HttpOverrides { class MyHttpOverride extends HttpOverrides {
@override @override
HttpClient createHttpClient(SecurityContext context) { HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context) return super.createHttpClient(context)
..badCertificateCallback = (cert, host, port) { ..badCertificateCallback = (cert, host, port) {
return host == "devweb.local"; // Forcefully trust local website return host == "devweb.local"; // Forcefully trust local website

View File

@ -1,5 +1,3 @@
import 'package:flutter/widgets.dart';
/// Account image settings /// Account image settings
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
@ -12,10 +10,8 @@ class AccountImageSettings {
final AccountImageVisibilityLevels visibility; final AccountImageVisibilityLevels visibility;
const AccountImageSettings({ const AccountImageSettings({
@required this.hasImage, required this.hasImage,
@required this.imageURL, required this.imageURL,
@required this.visibility, required this.visibility,
}) : assert(hasImage != null), });
assert(imageURL != null),
assert(visibility != null);
} }

View File

@ -1,7 +1,6 @@
import 'package:comunic/enums/likes_type.dart'; import 'package:comunic/enums/likes_type.dart';
import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/conversation.dart';
import 'package:comunic/models/like_element.dart'; import 'package:comunic/models/like_element.dart';
import 'package:flutter/material.dart';
import 'group.dart'; import 'group.dart';
@ -10,36 +9,35 @@ import 'group.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class AdvancedGroupInfo extends Group implements LikeElement { class AdvancedGroupInfo extends Group implements LikeElement {
bool isMembersListPublic; bool? isMembersListPublic;
final int timeCreate; final int? timeCreate;
String description; String description;
String url; String url;
int likes; int likes;
bool userLike; bool userLike;
List<Conversation> conversations; List<Conversation>? conversations;
bool isForezGroup; bool isForezGroup;
AdvancedGroupInfo({ AdvancedGroupInfo({
@required int id, required int id,
@required String name, required String name,
@required String iconURL, required String iconURL,
@required int numberMembers, required int numberMembers,
@required GroupMembershipLevel membershipLevel, required GroupMembershipLevel membershipLevel,
@required GroupVisibilityLevel visibilityLevel, required GroupVisibilityLevel visibilityLevel,
@required GroupRegistrationLevel registrationLevel, required GroupRegistrationLevel registrationLevel,
@required GroupPostCreationLevel postCreationLevel, required GroupPostCreationLevel postCreationLevel,
@required String virtualDirectory, required String virtualDirectory,
@required bool following, required bool following,
@required this.isMembersListPublic, required this.isMembersListPublic,
@required this.timeCreate, required this.timeCreate,
@required this.description, required this.description,
@required this.url, required this.url,
@required this.likes, required this.likes,
@required this.userLike, required this.userLike,
@required this.conversations, required this.conversations,
@required this.isForezGroup, required this.isForezGroup,
}) : assert(isForezGroup != null), }) : super(
super(
id: id, id: id,
name: name, name: name,
iconURL: iconURL, iconURL: iconURL,
@ -54,8 +52,7 @@ class AdvancedGroupInfo extends Group implements LikeElement {
@override @override
LikesType likeType = LikesType.GROUP; LikesType likeType = LikesType.GROUP;
get hasURL => url != null && url.isNotEmpty && url != "null"; get hasURL => url.isNotEmpty && url != "null";
get hasDescription => get hasDescription => description.isNotEmpty && description != "null";
description != null && description.isNotEmpty && description != "null";
} }

View File

@ -3,51 +3,42 @@ import 'package:comunic/enums/user_page_visibility.dart';
import 'package:comunic/lists/custom_emojies_list.dart'; import 'package:comunic/lists/custom_emojies_list.dart';
import 'package:comunic/models/like_element.dart'; import 'package:comunic/models/like_element.dart';
import 'package:comunic/models/user.dart'; import 'package:comunic/models/user.dart';
import 'package:meta/meta.dart';
/// Advanced user information /// Advanced user information
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
class AdvancedUserInfo extends User implements LikeElement { class AdvancedUserInfo extends User implements LikeElement {
final String emailAddress; final String? emailAddress;
final String publicNote; final String publicNote;
final bool canPostTexts; final bool canPostTexts;
final bool isFriendsListPublic; final bool isFriendsListPublic;
final int numberFriends; final int numberFriends;
final int accountCreationTime; final int accountCreationTime;
final String personalWebsite; final String personalWebsite;
final String location; final String? location;
bool userLike; bool userLike;
int likes; int likes;
AdvancedUserInfo({ AdvancedUserInfo({
@required int id, required int id,
@required String firstName, required String firstName,
@required String lastName, required String lastName,
@required UserPageVisibility pageVisibility, required UserPageVisibility pageVisibility,
@required String virtualDirectory, required String? virtualDirectory,
@required String accountImageURL, required String accountImageURL,
@required CustomEmojiesList customEmojies, required CustomEmojiesList customEmojies,
@required this.emailAddress, required this.emailAddress,
@required this.publicNote, required this.publicNote,
@required this.canPostTexts, required this.canPostTexts,
@required this.isFriendsListPublic, required this.isFriendsListPublic,
@required this.numberFriends, required this.numberFriends,
@required this.accountCreationTime, required this.accountCreationTime,
@required this.personalWebsite, required this.personalWebsite,
@required this.location, required this.location,
@required this.userLike, required this.userLike,
@required this.likes, required this.likes,
}) : assert(publicNote != null), }) : super(
assert(canPostTexts != null),
assert(isFriendsListPublic != null),
assert(numberFriends != null),
assert(accountCreationTime != null),
assert(personalWebsite != null),
assert(userLike != null),
assert(likes != null),
super(
id: id, id: id,
firstName: firstName, firstName: firstName,
lastName: lastName, lastName: lastName,
@ -60,9 +51,9 @@ class AdvancedUserInfo extends User implements LikeElement {
bool get hasPersonalWebsite => personalWebsite.isNotEmpty; bool get hasPersonalWebsite => personalWebsite.isNotEmpty;
bool get hasEmailAddress => emailAddress != null && emailAddress.isNotEmpty; bool get hasEmailAddress => emailAddress != null && emailAddress!.isNotEmpty;
bool get hasLocation => location != null && location.isNotEmpty; bool get hasLocation => location != null && location!.isNotEmpty;
@override @override
LikesType get likeType => LikesType.USER; LikesType get likeType => LikesType.USER;

View File

@ -4,7 +4,6 @@ import 'package:comunic/helpers/api_helper.dart';
import 'package:comunic/models/api_response.dart'; import 'package:comunic/models/api_response.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:meta/meta.dart';
/// API Request model /// API Request model
/// ///
@ -14,8 +13,8 @@ import 'package:meta/meta.dart';
class BytesFile { class BytesFile {
final String filename; final String filename;
final List<int> bytes; final List<int>? bytes;
final MediaType type; final MediaType? type;
const BytesFile( const BytesFile(
this.filename, this.filename,
@ -27,42 +26,36 @@ class BytesFile {
class APIRequest { class APIRequest {
final String uri; final String uri;
final bool needLogin; final bool needLogin;
ProgressCallback progressCallback; ProgressCallback? progressCallback;
CancelToken cancelToken; CancelToken? cancelToken;
Map<String, String> args; Map<String, String?>? args;
Map<String, File> files = Map(); Map<String, File> files = Map();
Map<String, BytesFile> bytesFiles = Map(); Map<String, BytesFile?> bytesFiles = Map();
APIRequest({@required this.uri, this.needLogin = false, this.args}) APIRequest({required this.uri, this.needLogin = false, this.args}) {
: assert(uri != null),
assert(needLogin != null) {
if (this.args == null) this.args = Map(); if (this.args == null) this.args = Map();
} }
APIRequest.withLogin(this.uri, {this.args}) APIRequest.withLogin(this.uri, {this.args}) : needLogin = true {
: needLogin = true,
assert(uri != null) {
if (args == null) this.args = Map(); if (args == null) this.args = Map();
} }
APIRequest.withoutLogin(this.uri, {this.args}) APIRequest.withoutLogin(this.uri, {this.args}) : needLogin = false {
: needLogin = false,
assert(uri != null) {
if (args == null) this.args = Map(); if (args == null) this.args = Map();
} }
APIRequest addString(String name, String value) { APIRequest addString(String name, String? value) {
args[name] = value; args![name] = value;
return this; return this;
} }
APIRequest addInt(String name, int value) { APIRequest addInt(String name, int? value) {
args[name] = value.toString(); args![name] = value.toString();
return this; return this;
} }
APIRequest addBool(String name, bool value) { APIRequest addBool(String name, bool value) {
args[name] = value ? "true" : "false"; args![name] = value ? "true" : "false";
return this; return this;
} }
@ -71,12 +64,12 @@ class APIRequest {
return this; return this;
} }
APIRequest addBytesFile(String name, BytesFile file) { APIRequest addBytesFile(String name, BytesFile? file) {
this.bytesFiles[name] = file; this.bytesFiles[name] = file;
return this; return this;
} }
void addArgs(Map<String, String> newArgs) => args.addAll(newArgs); void addArgs(Map<String, String> newArgs) => args!.addAll(newArgs);
/// Execute the request /// Execute the request
Future<APIResponse> exec() async => APIHelper().exec(this); Future<APIResponse> exec() async => APIHelper().exec(this);

View File

@ -6,13 +6,13 @@ import 'dart:convert';
class APIResponse { class APIResponse {
final int code; final int code;
final String content; final String? content;
const APIResponse(this.code, this.content) : assert(code != null); const APIResponse(this.code, this.content);
List<dynamic> getArray() => jsonDecode(this.content); List<dynamic>? getArray() => jsonDecode(this.content!);
Map<String, dynamic> getObject() => jsonDecode(this.content); Map<String, dynamic> getObject() => jsonDecode(this.content!);
/// Check if the request is successful or not /// Check if the request is successful or not
bool get isOK => code == 200; bool get isOK => code == 200;

View File

@ -1,4 +1,4 @@
import 'package:flutter/cupertino.dart';
/// Application settings /// Application settings
/// ///
@ -10,10 +10,8 @@ class ApplicationPreferences {
bool showPerformancesOverlay; bool showPerformancesOverlay;
ApplicationPreferences({ ApplicationPreferences({
@required this.enableDarkMode, required this.enableDarkMode,
@required this.forceMobileMode, required this.forceMobileMode,
@required this.showPerformancesOverlay, required this.showPerformancesOverlay,
}) : assert(enableDarkMode != null), }) ;
assert(forceMobileMode != null),
assert(showPerformancesOverlay != null);
} }

View File

@ -1,4 +1,4 @@
import 'package:meta/meta.dart';
/// Authentication details /// Authentication details
/// ///
@ -8,7 +8,5 @@ class AuthenticationDetails {
final String email; final String email;
final String password; final String password;
const AuthenticationDetails({@required this.email, @required this.password}) const AuthenticationDetails({required this.email, required this.password});
: assert(email != null),
assert(password != null);
} }

View File

@ -1,5 +1,4 @@
import 'package:comunic/helpers/database/database_contract.dart'; import 'package:comunic/helpers/database/database_contract.dart';
import 'package:meta/meta.dart';
/// Cache base model /// Cache base model
/// ///
@ -8,7 +7,7 @@ import 'package:meta/meta.dart';
abstract class CacheModel { abstract class CacheModel {
final int id; final int id;
const CacheModel({@required this.id}) : assert(id != null); const CacheModel({required this.id});
/// Initialize a CacheModel from a map /// Initialize a CacheModel from a map
CacheModel.fromMap(Map<String, dynamic> map) CacheModel.fromMap(Map<String, dynamic> map)

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
/// Call configuration /// Call configuration
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
@ -8,8 +6,8 @@ class CallConfig {
final List<String> iceServers; final List<String> iceServers;
const CallConfig({ const CallConfig({
@required this.iceServers, required this.iceServers,
}) : assert(iceServers != null); });
/// Turn this call configuration into the right for the WebRTC plugin /// Turn this call configuration into the right for the WebRTC plugin
Map<String, dynamic> get pluginConfig => { Map<String, dynamic> get pluginConfig => {

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart';
/// Single call member information /// Single call member information
@ -10,14 +9,13 @@ enum MemberStatus { JOINED, READY }
class CallMember { class CallMember {
final int userID; final int userID;
MemberStatus status; MemberStatus status;
MediaStream stream; MediaStream? stream;
CallMember({ CallMember({
@required this.userID, required this.userID,
this.status = MemberStatus.JOINED, this.status = MemberStatus.JOINED,
}) : assert(userID != null), });
assert(status != null);
bool get hasVideoStream => bool get hasVideoStream =>
stream != null && stream.getVideoTracks().length > 0; stream != null && stream!.getVideoTracks().length > 0;
} }

View File

@ -2,7 +2,6 @@ import 'package:comunic/enums/likes_type.dart';
import 'package:comunic/models/displayed_content.dart'; import 'package:comunic/models/displayed_content.dart';
import 'package:comunic/models/like_element.dart'; import 'package:comunic/models/like_element.dart';
import 'package:comunic/utils/account_utils.dart' as account; import 'package:comunic/utils/account_utils.dart' as account;
import 'package:meta/meta.dart';
/// Comments /// Comments
/// ///
@ -16,29 +15,22 @@ class Comment implements LikeElement {
final int postID; final int postID;
final int timeSent; final int timeSent;
DisplayedString content; DisplayedString content;
final String imageURL; final String? imageURL;
int likes; int likes;
bool userLike; bool userLike;
Comment({ Comment({
@required this.id, required this.id,
@required this.userID, required this.userID,
@required this.postID, required this.postID,
@required this.timeSent, required this.timeSent,
@required this.content, required this.content,
@required this.imageURL, required this.imageURL,
@required this.likes, required this.likes,
@required this.userLike, required this.userLike,
}) : assert(id != null), });
assert(userID != null),
assert(postID != null),
assert(timeSent != null),
assert(content != null),
assert(likes != null),
assert(userLike != null);
bool get hasContent => bool get hasContent => !content.isNull && content.length > 0;
content != null && !content.isNull && content.length > 0;
bool get hasImage => imageURL != null; bool get hasImage => imageURL != null;

View File

@ -1,8 +1,5 @@
import 'dart:ui';
import 'package:comunic/ui/routes/tour_route.dart'; import 'package:comunic/ui/routes/tour_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
/// Application configuration model /// Application configuration model
/// ///
@ -19,27 +16,27 @@ class Config {
// Theme customization // Theme customization
final Color splashBackgroundColor; final Color splashBackgroundColor;
final Color primaryColor; final Color? primaryColor;
final Color primaryColorDark; final Color? primaryColorDark;
final String appName; final String appName;
final String appQuickDescription; final String? appQuickDescription;
final Color unreadConversationColor; final Color? unreadConversationColor;
final Color defaultConversationColor; final Color? defaultConversationColor;
// Entries for the welcome tour // Entries for the welcome tour
final TourEntriesBuilder toursEntriesBuilder; final TourEntriesBuilder? toursEntriesBuilder;
// Custom initialization // Custom initialization
final Future<void> Function() additionalLoading; final Future<void> Function()? additionalLoading;
// Custom main application route // Custom main application route
final Widget Function(BuildContext, GlobalKey) mainRouteBuilder; final Widget Function(BuildContext, GlobalKey)? mainRouteBuilder;
const Config({ const Config({
@required this.apiServerName, required this.apiServerName,
@required this.apiServerUri, required this.apiServerUri,
@required this.apiServerSecure, required this.apiServerSecure,
@required this.clientName, required this.clientName,
this.splashBackgroundColor = defaultColor, this.splashBackgroundColor = defaultColor,
this.primaryColor, this.primaryColor,
this.primaryColorDark, this.primaryColorDark,
@ -50,17 +47,12 @@ class Config {
this.toursEntriesBuilder, this.toursEntriesBuilder,
this.additionalLoading, this.additionalLoading,
this.mainRouteBuilder, this.mainRouteBuilder,
}) : assert(apiServerName != null), });
assert(apiServerUri != null),
assert(apiServerSecure != null),
assert(clientName != null),
assert(splashBackgroundColor != null),
assert(appName != null);
/// Get and set static configuration /// Get and set static configuration
static Config _config; static Config? _config;
static Config get() { static Config? get() {
return _config; return _config;
} }
@ -71,5 +63,5 @@ class Config {
/// Get the current configuration of the application /// Get the current configuration of the application
Config config() { Config config() {
return Config.get(); return Config.get()!;
} }

View File

@ -1,8 +1,8 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:comunic/helpers/serialization/base_serialization_helper.dart'; import 'package:comunic/helpers/serialization/base_serialization_helper.dart';
import 'package:comunic/models/conversation_member.dart'; import 'package:comunic/models/conversation_member.dart';
import 'package:comunic/utils/account_utils.dart'; import 'package:comunic/utils/account_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'group.dart'; import 'group.dart';
@ -13,44 +13,38 @@ import 'group.dart';
enum CallCapabilities { NONE, AUDIO, VIDEO } enum CallCapabilities { NONE, AUDIO, VIDEO }
class Conversation extends SerializableElement<Conversation> { class Conversation extends SerializableElement<Conversation> {
final int id; final int? id;
final int lastActivity; final int? lastActivity;
final String name; final String? name;
final Color color; final Color? color;
final String logoURL; final String? logoURL;
final int groupID; final int? groupID;
final GroupMembershipLevel groupMinMembershipLevel; final GroupMembershipLevel? groupMinMembershipLevel;
final List<ConversationMember> members; final List<ConversationMember>? members;
final bool canEveryoneAddMembers; final bool? canEveryoneAddMembers;
final CallCapabilities callCapabilities; final CallCapabilities callCapabilities;
final bool isHavingCall; final bool isHavingCall;
Conversation({ Conversation({
@required this.id, /*required*/ required int this.id,
@required this.lastActivity, /*required*/ required int this.lastActivity,
@required this.name, required this.name,
@required this.color, required this.color,
@required this.logoURL, required this.logoURL,
@required this.groupID, required this.groupID,
@required this.groupMinMembershipLevel, required this.groupMinMembershipLevel,
@required this.members, /*required*/ required List<ConversationMember> this.members,
@required this.canEveryoneAddMembers, /*required*/ required bool this.canEveryoneAddMembers,
this.callCapabilities = CallCapabilities.NONE, this.callCapabilities = CallCapabilities.NONE,
this.isHavingCall = false, this.isHavingCall = false,
}) : assert(id != null), }) : assert((groupID == null) == (groupMinMembershipLevel == null));
assert(lastActivity != null),
assert(members != null),
assert(canEveryoneAddMembers != null),
assert((groupID == null) == (groupMinMembershipLevel == null)),
assert(callCapabilities != null),
assert(isHavingCall != null);
/// Check out whether a conversation has a fixed name or not /// Check out whether a conversation has a fixed name or not
bool get hasName => this.name != null; bool get hasName => this.name != null;
/// Get current user membership /// Get current user membership
ConversationMember get membership => ConversationMember get membership =>
members.firstWhere((m) => m.userID == userID()); members!.firstWhere((m) => m.userID == userID());
/// Check out whether current user of the application is an admin /// Check out whether current user of the application is an admin
bool get isAdmin => membership.isAdmin; bool get isAdmin => membership.isAdmin;
@ -62,17 +56,17 @@ class Conversation extends SerializableElement<Conversation> {
bool get following => membership.following; bool get following => membership.following;
/// Get the list of members in the conversation /// Get the list of members in the conversation
Set<int> get membersID => members.map((e) => e.userID).toSet(); Set<int?> get membersID => members!.map((e) => e.userID).toSet();
/// Get the list of admins in the conversation /// Get the list of admins in the conversation
Set<int> get adminsID => Set<int?> get adminsID =>
members.where((e) => e.isAdmin).map((e) => e.userID).toSet(); members!.where((e) => e.isAdmin).map((e) => e.userID).toSet();
/// Get the list of the OTHER members of the conversation (all except current user) /// Get the list of the OTHER members of the conversation (all except current user)
Set<int> get otherMembersID => membersID..remove(userID()); Set<int?> get otherMembersID => membersID..remove(userID());
/// Check if the last message has been seen or not /// Check if the last message has been seen or not
bool get sawLastMessage => lastActivity <= membership.lastAccessTime; bool get sawLastMessage => lastActivity! <= membership.lastAccessTime;
/// Check out whether a conversation is managed or not /// Check out whether a conversation is managed or not
bool get isManaged => isGroupConversation; bool get isManaged => isGroupConversation;
@ -87,9 +81,8 @@ class Conversation extends SerializableElement<Conversation> {
color = map["color"] == null ? null : Color(map["color"]), color = map["color"] == null ? null : Color(map["color"]),
logoURL = map["logoURL"], logoURL = map["logoURL"],
groupID = map["groupID"], groupID = map["groupID"],
groupMinMembershipLevel = GroupMembershipLevel.values.firstWhere( groupMinMembershipLevel = GroupMembershipLevel.values.firstWhereOrNull(
(element) => element.toString() == map["groupMinMembershipLevel"], (element) => element.toString() == map["groupMinMembershipLevel"]),
orElse: () => null),
lastActivity = map["lastActivity"], lastActivity = map["lastActivity"],
members = map["members"] members = map["members"]
.map((el) => ConversationMember.fromJSON(el)) .map((el) => ConversationMember.fromJSON(el))
@ -110,13 +103,13 @@ class Conversation extends SerializableElement<Conversation> {
"groupID": groupID, "groupID": groupID,
"groupMinMembershipLevel": groupMinMembershipLevel?.toString(), "groupMinMembershipLevel": groupMinMembershipLevel?.toString(),
"lastActivity": lastActivity, "lastActivity": lastActivity,
"members": members.map((e) => e.toJson()).toList(), "members": members!.map((e) => e.toJson()).toList(),
"canEveryoneAddMembers": canEveryoneAddMembers, "canEveryoneAddMembers": canEveryoneAddMembers,
}; };
} }
@override @override
int compareTo(Conversation other) { int compareTo(Conversation other) {
return other.lastActivity.compareTo(this.lastActivity); return other.lastActivity!.compareTo(this.lastActivity!);
} }
} }

View File

@ -1,5 +1,3 @@
import 'package:flutter/widgets.dart';
/// Conversation member /// Conversation member
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
@ -12,16 +10,12 @@ class ConversationMember {
final bool isAdmin; final bool isAdmin;
const ConversationMember({ const ConversationMember({
@required this.userID, required this.userID,
@required this.lastMessageSeen, required this.lastMessageSeen,
@required this.lastAccessTime, required this.lastAccessTime,
@required this.following, required this.following,
@required this.isAdmin, required this.isAdmin,
}) : assert(userID != null), });
assert(lastMessageSeen != null),
assert(lastAccessTime != null),
assert(following != null),
assert(isAdmin != null);
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'userID': userID, 'userID': userID,

View File

@ -4,7 +4,6 @@ import 'package:comunic/models/displayed_content.dart';
import 'package:comunic/utils/account_utils.dart' as account; import 'package:comunic/utils/account_utils.dart' as account;
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
/// Single conversation message /// Single conversation message
/// ///
@ -31,27 +30,24 @@ const _ConversationFileMimeTypeMapping = {
}; };
class ConversationMessageFile { class ConversationMessageFile {
final String url; final String? url;
final int size; final int? size;
final String name; final String? name;
final String thumbnail; final String? thumbnail;
final String type; final String? type;
const ConversationMessageFile({ const ConversationMessageFile({
@required this.url, required String this.url,
@required this.size, required int this.size,
@required this.name, required String this.name,
@required this.thumbnail, required this.thumbnail,
@required this.type, required String this.type,
}) : assert(url != null), });
assert(size != null),
assert(name != null),
assert(type != null);
/// Get the type of file /// Get the type of file
ConversationMessageFileType get fileType { ConversationMessageFileType? get fileType {
if (type != null && _ConversationFileMimeTypeMapping.containsKey(type)) if (type != null && _ConversationFileMimeTypeMapping.containsKey(type))
return _ConversationFileMimeTypeMapping[type]; return _ConversationFileMimeTypeMapping[type!];
else else
return ConversationMessageFileType.OTHER; return ConversationMessageFileType.OTHER;
} }
@ -103,21 +99,20 @@ enum ConversationServerMessageType {
class ConversationServerMessage { class ConversationServerMessage {
final ConversationServerMessageType type; final ConversationServerMessageType type;
final int userID; final int? userID;
final int userWhoAdded; final int? userWhoAdded;
final int userAdded; final int? userAdded;
final int userWhoRemoved; final int? userWhoRemoved;
final int userRemoved; final int? userRemoved;
const ConversationServerMessage({ const ConversationServerMessage({
@required this.type, required this.type,
@required this.userID, required this.userID,
@required this.userWhoAdded, required this.userWhoAdded,
@required this.userAdded, required this.userAdded,
@required this.userWhoRemoved, required this.userWhoRemoved,
@required this.userRemoved, required this.userRemoved,
}) : assert(type != null), }) : assert(userID != null ||
assert(userID != null ||
(type != ConversationServerMessageType.USER_CREATED_CONVERSATION && (type != ConversationServerMessageType.USER_CREATED_CONVERSATION &&
type != ConversationServerMessageType.USER_LEFT_CONV)), type != ConversationServerMessageType.USER_LEFT_CONV)),
assert((userWhoAdded != null && userAdded != null) || assert((userWhoAdded != null && userAdded != null) ||
@ -125,47 +120,47 @@ class ConversationServerMessage {
assert((userWhoRemoved != null && userRemoved != null) || assert((userWhoRemoved != null && userRemoved != null) ||
type != ConversationServerMessageType.USER_REMOVED_ANOTHER_USER); type != ConversationServerMessageType.USER_REMOVED_ANOTHER_USER);
Set<int> get usersID { Set<int?> get usersID {
switch (type) { switch (type) {
case ConversationServerMessageType.USER_CREATED_CONVERSATION: case ConversationServerMessageType.USER_CREATED_CONVERSATION:
case ConversationServerMessageType.USER_LEFT_CONV: case ConversationServerMessageType.USER_LEFT_CONV:
return Set()..add(userID); return Set()..add(userID);
case ConversationServerMessageType.USER_ADDED_ANOTHER_USER: case ConversationServerMessageType.USER_ADDED_ANOTHER_USER:
return Set()..add(userWhoAdded)..add(userAdded); return Set()
..add(userWhoAdded)
..add(userAdded);
case ConversationServerMessageType.USER_REMOVED_ANOTHER_USER: case ConversationServerMessageType.USER_REMOVED_ANOTHER_USER:
return Set()..add(userWhoRemoved)..add(userRemoved); return Set()
..add(userWhoRemoved)
..add(userRemoved);
}
} }
throw Exception("Unsupported server message type!"); String? getText(UsersList? list) {
}
String getText(UsersList list) {
switch (type) { switch (type) {
case ConversationServerMessageType.USER_CREATED_CONVERSATION: case ConversationServerMessageType.USER_CREATED_CONVERSATION:
return tr("%1% created the conversation", return tr("%1% created the conversation",
args: {"1": list.getUser(userID).fullName}); args: {"1": list!.getUser(userID).fullName});
case ConversationServerMessageType.USER_ADDED_ANOTHER_USER: case ConversationServerMessageType.USER_ADDED_ANOTHER_USER:
return tr("%1% added %2% to the conversation", args: { return tr("%1% added %2% to the conversation", args: {
"1": list.getUser(userWhoAdded).fullName, "1": list!.getUser(userWhoAdded).fullName,
"2": list.getUser(userAdded).fullName, "2": list.getUser(userAdded).fullName,
}); });
case ConversationServerMessageType.USER_LEFT_CONV: case ConversationServerMessageType.USER_LEFT_CONV:
return tr("%1% left the conversation", args: { return tr("%1% left the conversation", args: {
"1": list.getUser(userID).fullName, "1": list!.getUser(userID).fullName,
}); });
case ConversationServerMessageType.USER_REMOVED_ANOTHER_USER: case ConversationServerMessageType.USER_REMOVED_ANOTHER_USER:
return tr("%1% removed %2% from the conversation", args: { return tr("%1% removed %2% from the conversation", args: {
"1": list.getUser(userWhoRemoved).fullName, "1": list!.getUser(userWhoRemoved).fullName,
"2": list.getUser(userRemoved).fullName, "2": list.getUser(userRemoved).fullName,
}); });
} }
throw Exception("Unsupported message type!");
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -188,29 +183,26 @@ class ConversationServerMessage {
} }
class ConversationMessage extends SerializableElement<ConversationMessage> { class ConversationMessage extends SerializableElement<ConversationMessage> {
final int id; final int? id;
final int convID; final int? convID;
final int userID; final int? userID;
final int timeSent; final int? timeSent;
final DisplayedString message; final DisplayedString message;
final ConversationMessageFile file; final ConversationMessageFile? file;
final ConversationServerMessage serverMessage; final ConversationServerMessage? serverMessage;
ConversationMessage({ ConversationMessage({
@required this.id, required int this.id,
@required this.convID, required int this.convID,
@required this.userID, required this.userID,
@required this.timeSent, required int this.timeSent,
@required this.message, required this.message,
@required this.file, required this.file,
@required this.serverMessage, required this.serverMessage,
}) : assert(id != null), }) : assert(userID != null || serverMessage != null),
assert(convID != null), assert(!message.isNull || file != null || serverMessage != null);
assert(userID != null || serverMessage != null),
assert(timeSent != null),
assert(message != null || file != null || serverMessage != null);
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timeSent * 1000); DateTime get date => DateTime.fromMillisecondsSinceEpoch(timeSent! * 1000);
bool get hasMessage => !message.isNull && message.length > 0; bool get hasMessage => !message.isNull && message.length > 0;
@ -221,16 +213,16 @@ class ConversationMessage extends SerializableElement<ConversationMessage> {
bool get isServerMessage => serverMessage != null; bool get isServerMessage => serverMessage != null;
/// Get the list of the ID of the users implied in this message /// Get the list of the ID of the users implied in this message
Set<int> get usersID { Set<int?> get usersID {
if (userID != null) return Set()..add(userID); if (userID != null) return Set()..add(userID);
if (serverMessage != null) return serverMessage.usersID; if (serverMessage != null) return serverMessage!.usersID;
return Set(); return Set();
} }
@override @override
int compareTo(ConversationMessage other) { int compareTo(ConversationMessage other) {
return id.compareTo(other.id); return id!.compareTo(other.id!);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View File

@ -3,12 +3,11 @@
/// @author Pierre Hubert /// @author Pierre Hubert
class CountUnreadNotifications { class CountUnreadNotifications {
int notifications; int? notifications;
int conversations; int? conversations;
CountUnreadNotifications({ CountUnreadNotifications({
this.notifications, required int this.notifications,
this.conversations, required int this.conversations,
}) : assert(notifications != null), });
assert(conversations != null);
} }

View File

@ -1,24 +1,19 @@
import 'package:flutter/material.dart';
/// Single custom emoji information /// Single custom emoji information
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
class CustomEmoji { class CustomEmoji {
final int id; final int? id;
final int userID; final int? userID;
final String shortcut; final String? shortcut;
final String url; final String? url;
const CustomEmoji({ const CustomEmoji({
@required this.id, required int this.id,
@required this.userID, required int this.userID,
@required this.shortcut, required String this.shortcut,
@required this.url, required String this.url,
}) : assert(id != null), });
assert(userID != null),
assert(shortcut != null),
assert(url != null);
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
"id": id, "id": id,

View File

@ -3,12 +3,12 @@
/// @author Pierre Hubert /// @author Pierre Hubert
class DataConservationPolicySettings { class DataConservationPolicySettings {
int inactiveAccountLifeTime; int? inactiveAccountLifeTime;
int notificationLifetime; int? notificationLifetime;
int commentsLifetime; int? commentsLifetime;
int postsLifetime; int? postsLifetime;
int conversationMessagesLifetime; int? conversationMessagesLifetime;
int likesLifetime; int? likesLifetime;
DataConservationPolicySettings({ DataConservationPolicySettings({
this.inactiveAccountLifeTime, this.inactiveAccountLifeTime,

View File

@ -5,32 +5,32 @@ import 'package:comunic/utils/ui_utils.dart';
/// @author Pierre Hubert /// @author Pierre Hubert
class DisplayedString { class DisplayedString {
String _string; String? _string;
String _parseCache; String? _parseCache;
DisplayedString(this._string); DisplayedString(this._string);
int get length => _string.length; int get length => _string!.length;
bool get isEmpty => _string.isEmpty; bool get isEmpty => _string!.isEmpty;
bool get isNull => _string == null; bool get isNull => _string == null;
String get content => _string; String? get content => _string;
set content(String content) { set content(String? content) {
_string = content; _string = content;
_parseCache = null; _parseCache = null;
} }
@override @override
String toString() { String toString() {
return _string; return _string!;
} }
String get parsedString { String? get parsedString {
if (_parseCache == null) { if (_parseCache == null) {
_parseCache = parseEmojies(this._string); _parseCache = parseEmojies(this._string!);
} }
return _parseCache; return _parseCache;

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
/// Single presence information /// Single presence information
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
@ -11,19 +9,14 @@ class Presence {
final int day; final int day;
const Presence({ const Presence({
@required this.userID, required this.userID,
@required this.year, required this.year,
@required this.month, required this.month,
@required this.day, required this.day,
}) : assert(userID != null), });
assert(year != null),
assert(month != null),
assert(day != null);
Presence.fromDateTime(DateTime dt, this.userID) Presence.fromDateTime(DateTime dt, this.userID)
: assert(dt != null), : year = dt.year,
year = dt.year,
month = dt.month, month = dt.month,
day = dt.day, day = dt.day;
assert(userID != null);
} }

View File

@ -1,7 +1,6 @@
import 'package:comunic/helpers/database/database_contract.dart'; import 'package:comunic/helpers/database/database_contract.dart';
import 'package:comunic/models/cache_model.dart'; import 'package:comunic/models/cache_model.dart';
import 'package:comunic/utils/date_utils.dart'; import 'package:comunic/utils/date_utils.dart';
import 'package:meta/meta.dart';
/// Single user Friend information /// Single user Friend information
/// ///
@ -9,28 +8,23 @@ import 'package:meta/meta.dart';
class Friend extends CacheModel implements Comparable<Friend> { class Friend extends CacheModel implements Comparable<Friend> {
bool accepted; bool accepted;
final int lastActive; final int? lastActive;
final bool following; final bool following;
final bool canPostTexts; final bool canPostTexts;
Friend({ Friend({
@required int id, required int id,
@required this.accepted, required this.accepted,
@required this.lastActive, required int this.lastActive,
@required this.following, required this.following,
@required this.canPostTexts, required this.canPostTexts,
}) : assert(id != null), }) : super(id: id);
assert(accepted != null),
assert(lastActive != null),
assert(following != null),
assert(canPostTexts != null),
super(id: id);
/// Check out whether friend is connected or not /// Check out whether friend is connected or not
bool get isConnected => time() - 30 < lastActive; bool get isConnected => time() - 30 < lastActive!;
@override @override
int compareTo(Friend other) => other.lastActive.compareTo(lastActive); int compareTo(Friend other) => other.lastActive!.compareTo(lastActive!);
@override @override
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
@ -42,8 +36,7 @@ class Friend extends CacheModel implements Comparable<Friend> {
}; };
Friend.fromMap(Map<String, dynamic> map) Friend.fromMap(Map<String, dynamic> map)
: assert(map != null), : accepted = map[FriendsListTableContract.C_ACCEPTED] == 1,
accepted = map[FriendsListTableContract.C_ACCEPTED] == 1,
lastActive = map[FriendsListTableContract.C_LAST_ACTIVE], lastActive = map[FriendsListTableContract.C_LAST_ACTIVE],
following = map[FriendsListTableContract.C_FOLLOWING] == 1, following = map[FriendsListTableContract.C_FOLLOWING] == 1,
canPostTexts = map[FriendsListTableContract.C_CAN_POST_TEXTS] == 1, canPostTexts = map[FriendsListTableContract.C_CAN_POST_TEXTS] == 1,

View File

@ -1,5 +1,3 @@
import 'package:meta/meta.dart';
/// Simple friendship status /// Simple friendship status
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
@ -12,16 +10,12 @@ class FriendStatus {
final bool following; final bool following;
const FriendStatus({ const FriendStatus({
@required this.userID, required this.userID,
@required this.areFriend, required this.areFriend,
@required this.sentRequest, required this.sentRequest,
@required this.receivedRequest, required this.receivedRequest,
@required this.following, required this.following,
}) : assert(userID != null), });
assert(areFriend != null),
assert(sentRequest != null),
assert(receivedRequest != null),
assert(following != null);
bool get noRequestExchanged => !areFriend && !sentRequest && !receivedRequest; bool get noRequestExchanged => !areFriend && !sentRequest && !receivedRequest;
} }

View File

@ -1,5 +1,4 @@
import 'package:comunic/enums/user_page_visibility.dart'; import 'package:comunic/enums/user_page_visibility.dart';
import 'package:flutter/material.dart';
/// General settings /// General settings
/// ///
@ -18,32 +17,21 @@ class GeneralSettings {
String virtualDirectory; String virtualDirectory;
String personalWebsite; String personalWebsite;
String publicNote; String publicNote;
String location; String? location;
GeneralSettings({ GeneralSettings({
@required this.email, required this.email,
@required this.firstName, required this.firstName,
@required this.lastName, required this.lastName,
@required this.pageVisibility, required this.pageVisibility,
@required this.allowComments, required this.allowComments,
@required this.allowPostsFromFriends, required this.allowPostsFromFriends,
@required this.allowComunicEmails, required this.allowComunicEmails,
@required this.publicFriendsList, required this.publicFriendsList,
@required this.publicEmail, required this.publicEmail,
@required this.virtualDirectory, required this.virtualDirectory,
@required this.personalWebsite, required this.personalWebsite,
@required this.publicNote, required this.publicNote,
@required this.location, required this.location,
}) : assert(email != null), });
assert(firstName != null),
assert(lastName != null),
assert(pageVisibility != null),
assert(allowComments != null),
assert(allowPostsFromFriends != null),
assert(allowComunicEmails != null),
assert(publicFriendsList != null),
assert(publicEmail != null),
assert(virtualDirectory != null),
assert(personalWebsite != null),
assert(publicNote != null);
} }

View File

@ -1,5 +1,4 @@
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:meta/meta.dart';
/// Group information /// Group information
/// ///
@ -17,19 +16,18 @@ enum GroupMembershipLevel {
String membershipToText(GroupMembershipLevel level) { String membershipToText(GroupMembershipLevel level) {
switch (level) { switch (level) {
case GroupMembershipLevel.ADMINISTRATOR: case GroupMembershipLevel.ADMINISTRATOR:
return tr("Administrator"); return tr("Administrator")!;
case GroupMembershipLevel.MODERATOR: case GroupMembershipLevel.MODERATOR:
return tr("Moderator"); return tr("Moderator")!;
case GroupMembershipLevel.MEMBER: case GroupMembershipLevel.MEMBER:
return tr("Member"); return tr("Member")!;
case GroupMembershipLevel.INVITED: case GroupMembershipLevel.INVITED:
return tr("Invited"); return tr("Invited")!;
case GroupMembershipLevel.PENDING: case GroupMembershipLevel.PENDING:
return tr("Requested"); return tr("Requested")!;
case GroupMembershipLevel.VISITOR: case GroupMembershipLevel.VISITOR:
return tr("Visitor"); return tr("Visitor")!;
} }
throw new Exception("Unreachable statement!");
} }
enum GroupVisibilityLevel { OPEN, PRIVATE, SECRETE } enum GroupVisibilityLevel { OPEN, PRIVATE, SECRETE }
@ -51,25 +49,17 @@ class Group implements Comparable<Group> {
bool following; bool following;
Group({ Group({
@required this.id, required this.id,
@required this.name, required this.name,
@required this.iconURL, required this.iconURL,
@required this.numberMembers, required this.numberMembers,
@required this.membershipLevel, required this.membershipLevel,
@required this.visibilityLevel, required this.visibilityLevel,
@required this.registrationLevel, required this.registrationLevel,
@required this.postCreationLevel, required this.postCreationLevel,
@required this.virtualDirectory, required this.virtualDirectory,
@required this.following, required this.following,
}) : assert(id != null), });
assert(name != null),
assert(iconURL != null),
assert(numberMembers != null),
assert(membershipLevel != null),
assert(visibilityLevel != null),
assert(registrationLevel != null),
assert(postCreationLevel != null),
assert(following != null);
get displayName => this.name; get displayName => this.name;

View File

@ -1,5 +1,4 @@
import 'package:comunic/models/group.dart'; import 'package:comunic/models/group.dart';
import 'package:flutter/material.dart';
/// Group membership information /// Group membership information
/// ///
@ -12,14 +11,11 @@ class GroupMembership {
final GroupMembershipLevel level; final GroupMembershipLevel level;
const GroupMembership({ const GroupMembership({
@required this.userID, required this.userID,
@required this.groupID, required this.groupID,
@required this.timeCreate, required this.timeCreate,
@required this.level, required this.level,
}) : assert(userID != null), });
assert(groupID != null),
assert(timeCreate != null),
assert(level != null);
String get membershipText => membershipToText(level); String get membershipText => membershipToText(level);

View File

@ -9,6 +9,6 @@ abstract class LikeElement {
int get id; int get id;
bool userLike; late bool userLike;
int likes; late int likes;
} }

View File

@ -1,6 +1,5 @@
import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/conversation.dart';
import 'package:comunic/models/friend.dart'; import 'package:comunic/models/friend.dart';
import 'package:flutter/material.dart';
/// Membership information /// Membership information
/// ///
@ -10,40 +9,37 @@ enum MembershipType { FRIEND, GROUP, CONVERSATION }
class Membership { class Membership {
final MembershipType type; final MembershipType type;
final Conversation conversation; final Conversation? conversation;
final Friend friend; final Friend? friend;
final int groupID; final int? groupID;
final int groupLastActive; final int? groupLastActive;
Membership.conversation(this.conversation) Membership.conversation(Conversation this.conversation)
: type = MembershipType.CONVERSATION, : type = MembershipType.CONVERSATION,
friend = null, friend = null,
groupID = null, groupID = null,
groupLastActive = null, groupLastActive = null;
assert(conversation != null);
Membership.friend(this.friend) Membership.friend(Friend this.friend)
: type = MembershipType.FRIEND, : type = MembershipType.FRIEND,
conversation = null, conversation = null,
groupID = null, groupID = null,
groupLastActive = null, groupLastActive = null;
assert(friend != null);
Membership.group({@required this.groupID, @required this.groupLastActive}) Membership.group(
{required int this.groupID, required int this.groupLastActive})
: type = MembershipType.GROUP, : type = MembershipType.GROUP,
conversation = null, conversation = null,
friend = null, friend = null;
assert(groupID != null),
assert(groupLastActive != null);
int get lastActive { int? get lastActive {
switch (type) { switch (type) {
case MembershipType.FRIEND: case MembershipType.FRIEND:
return friend.lastActive; return friend!.lastActive;
case MembershipType.GROUP: case MembershipType.GROUP:
return groupLastActive; return groupLastActive;
case MembershipType.CONVERSATION: case MembershipType.CONVERSATION:
return conversation.lastActivity; return conversation!.lastActivity;
default: default:
throw Exception("Unreachable statment!"); throw Exception("Unreachable statment!");
} }

View File

@ -1,5 +1,3 @@
import 'package:flutter/widgets.dart';
/// New account information container /// New account information container
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
@ -11,12 +9,9 @@ class NewAccount {
final String password; final String password;
NewAccount({ NewAccount({
@required this.firstName, required this.firstName,
@required this.lastName, required this.lastName,
@required this.email, required this.email,
@required this.password, required this.password,
}) : assert(firstName != null), });
assert(lastName != null),
assert(email != null),
assert(password != null);
} }

View File

@ -1,5 +1,3 @@
import 'package:meta/meta.dart';
import 'api_request.dart'; import 'api_request.dart';
/// New comment information /// New comment information
@ -9,15 +7,15 @@ import 'api_request.dart';
class NewComment { class NewComment {
final int postID; final int postID;
final String content; final String content;
final BytesFile image; final BytesFile? image;
const NewComment({ const NewComment({
@required this.postID, required this.postID,
@required this.content, required this.content,
@required this.image, required this.image,
}) : assert(postID != null); });
bool get hasContent => content != null && content.length > 0; bool get hasContent => content.length > 0;
bool get hasImage => image != null; bool get hasImage => image != null;
} }

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