Compare commits
397 Commits
Author | SHA1 | Date | |
---|---|---|---|
f3ccb4e89a | |||
24765789df | |||
52cc8407d5 | |||
523f9fd0e3 | |||
9ce1a401d9 | |||
a07c6cd415 | |||
a3e5b1a70b | |||
5e51fbe617 | |||
7c82aa5edf | |||
0db7de01ee | |||
ee6e28426c | |||
df915e1ca3 | |||
e33cdf56a9 | |||
bbcc3c9d02 | |||
6db9eade90 | |||
c759b54024 | |||
138d70a6e1 | |||
4e2974a2e2 | |||
e7073292b5 | |||
06aa84e3a1 | |||
ac2736dd23 | |||
9c53572f94 | |||
b337e632bd | |||
e0d69ab504 | |||
5086f45cc1 | |||
acc81acdea | |||
b4465cc70c | |||
ef0ae2a586 | |||
9697ba8149 | |||
d70c2751bb | |||
cac23dd620 | |||
3794eca68b | |||
e3c11e48b2 | |||
cfc3552011 | |||
f8ba06d0ae | |||
6528ef5bc2 | |||
bf2ba03912 | |||
e7181c1f59 | |||
9b0c1323b6 | |||
61098ffc43 | |||
b364c7aece | |||
288cd492a2 | |||
736accaca4 | |||
922fce541f | |||
bc3972082c | |||
fbe22c5031 | |||
7f1130aca8 | |||
313e7a4b3c | |||
e231f26231 | |||
9a2efaae1c | |||
67e881af0d | |||
a6ce969e89 | |||
d04b40f662 | |||
21d844b2ba | |||
4c11aa9753 | |||
dfcc49eab4 | |||
395b9aca6b | |||
70e3efc93d | |||
fe3f0c0e85 | |||
6b8cc2569d | |||
17f271dfb9 | |||
c747d3c1ba | |||
57e504540a | |||
9e5e93da39 | |||
f7bf69fbfd | |||
78ffc38534 | |||
466803a1ac | |||
39f4e8c78c | |||
4c749ec6ac | |||
620ad0d5cf | |||
f3eda1c89d | |||
a01ebe7d8a | |||
f4ced0324c | |||
5c26626f92 | |||
e7f360c6fc | |||
773cf17da0 | |||
1bd2bdd1aa | |||
c65558ef6c | |||
1309e7ad3a | |||
096fd42f22 | |||
27a56ae533 | |||
e3130c9e4b | |||
260002270f | |||
a119f60fdb | |||
a53ae381dc | |||
6bdaf83563 | |||
476f08681b | |||
cf9f93dcb3 | |||
1d7e846973 | |||
ef186f79d2 | |||
b40308c9e4 | |||
63a8e61e80 | |||
25222e9156 | |||
642820127c | |||
44f417a0f2 | |||
ce908d1b51 | |||
4db9191fd8 | |||
c262e40e81 | |||
27176531cf | |||
b23aa782b8 | |||
4367dcc728 | |||
6d95d5f7a2 | |||
db52c495ec | |||
32aa73a951 | |||
67c217715a | |||
f45bcd691e | |||
099f282cd6 | |||
032815b29f | |||
123eaad296 | |||
b020895a8c | |||
2cea14a012 | |||
38f0257fa3 | |||
150529a740 | |||
6cbe34379e | |||
782e6bc978 | |||
e7e81da05d | |||
af8e558d9f | |||
109ba3f04b | |||
68b4c79960 | |||
5ccd3d4884 | |||
6cb9fd97e2 | |||
fe17f81a40 | |||
b0fd0d7d51 | |||
13cd3186f5 | |||
3835ec4fe1 | |||
33eb0c5aed | |||
af079cc4b0 | |||
2311af8219 | |||
a64a192d27 | |||
5fb845732f | |||
935d7dbb63 | |||
ccb1547523 | |||
f41983304a | |||
edff7868f2 | |||
c7f970ae70 | |||
97db56ae78 | |||
89d3b79617 | |||
8943ae8144 | |||
de063bd797 | |||
24e08d63f8 | |||
1750d6079e | |||
0b1dfa460a | |||
cd82fac803 | |||
d8fa90fc6a | |||
fe163b3c69 | |||
86575a1e86 | |||
87e670a520 | |||
91be430dbb | |||
f31323fe23 | |||
6961694fd2 | |||
0103779608 | |||
5c4020d511 | |||
968321b12c | |||
eabb27495c | |||
263849266f | |||
c5c544fb34 | |||
286639889b | |||
3d0bfe6c3f | |||
d3132942bc | |||
28451eddbd | |||
86c89e782c | |||
5c76f6e0a6 | |||
e3c9105b1c | |||
0c2f3a28d0 | |||
47dbb90640 | |||
e00f452b98 | |||
11c25ea271 | |||
44ea647624 | |||
3ffb24f7c5 | |||
e78d526bbb | |||
91e7dd4019 | |||
6e274cae11 | |||
e1795bac03 | |||
d4a39a3527 | |||
a48e7f57a6 | |||
07adf8c2ca | |||
685565e031 | |||
eb66ea407b | |||
2edaedc5f3 | |||
3c7795837c | |||
2ec8693e85 | |||
804457c761 | |||
1227ef283c | |||
f179e7e1d5 | |||
e2bf5e73dd | |||
bff73dbb21 | |||
df111e393a | |||
9646cb7a70 | |||
4cb672cb16 | |||
1a53a26f39 | |||
f450a46e99 | |||
d6f2df7002 | |||
dbf2ed868a | |||
2d86780f0b | |||
cc08ed0232 | |||
971210e7e8 | |||
8f0f50d0e4 | |||
519c68b092 | |||
d889321b38 | |||
257523b526 | |||
0bb9be9a72 | |||
4c1e33a264 | |||
e204c62ba9 | |||
9b427b9683 | |||
02e60ad87c | |||
20ea964337 | |||
289bf30a40 | |||
29cc8558c3 | |||
d64d2ece05 | |||
ae9491e198 | |||
3cf9ee39d0 | |||
5d432a5f87 | |||
c0e2516f39 | |||
25f72bd11c | |||
225df61aa0 | |||
e6df696077 | |||
032b247080 | |||
c42fe7f806 | |||
75e15f9f83 | |||
88ea7e2431 | |||
f32ee89924 | |||
cd355f84cc | |||
38c1474d2e | |||
9e5decae61 | |||
cf0708cd3b | |||
991d7ec1ab | |||
6ba89b6bd1 | |||
2109c71dd6 | |||
da38ce426f | |||
8f927e9f72 | |||
ca1f94531f | |||
24f5a3e482 | |||
c09670ad64 | |||
ace7b44595 | |||
9a4b61aff3 | |||
1b5169eb56 | |||
8bd937420e | |||
e1f8ad1466 | |||
cbc0de944e | |||
da48328c92 | |||
bb62a3a159 | |||
4b0b41a902 | |||
c65d87dacc | |||
6b8e1f205b | |||
b2c4f33665 | |||
622258802b | |||
d79e132420 | |||
4e951c6a78 | |||
8af4a61072 | |||
ee5f791638 | |||
9a991b221c | |||
ba07247ec4 | |||
60c135bf01 | |||
3a9bb3d13e | |||
2cf017ad2d | |||
ae391ab691 | |||
de84fb6113 | |||
675d1b8588 | |||
7768e36c62 | |||
0ebccf1075 | |||
d94b535001 | |||
707577f9ac | |||
a74600ce4b | |||
d083728251 | |||
565f351d1e | |||
b9a329c8f0 | |||
a16618bb51 | |||
8609e4e169 | |||
e5ccedd180 | |||
6a9e0e36c1 | |||
de1dceae9b | |||
2927f72674 | |||
2858c50449 | |||
4988c8fea8 | |||
167217a5a0 | |||
7364fc49a8 | |||
ee6509bb9a | |||
e05323c3bb | |||
096251eaad | |||
45d903bcf7 | |||
473402149a | |||
80f05cb008 | |||
73c7fa8807 | |||
c0856c5126 | |||
99ecc399ee | |||
0f99e807f8 | |||
456df166b1 | |||
08d357fc72 | |||
9154fe47e1 | |||
08c2ac32aa | |||
04693cc163 | |||
391d3150dd | |||
da641515fa | |||
0a03f581d1 | |||
f227209e9b | |||
5eaf8d6b72 | |||
159e7228bf | |||
40cf1b1ddc | |||
cb47f0351e | |||
909e68e7bb | |||
88ba2d303e | |||
2746623b8d | |||
b0be889833 | |||
dab4e7bde1 | |||
6b08b62832 | |||
a60c1ed68c | |||
02034acbbe | |||
469e1e1f92 | |||
526f698bf4 | |||
a8c358fd58 | |||
0700014b3a | |||
1b13a90615 | |||
36f89a9a53 | |||
de3cd9c7b7 | |||
9021ca7168 | |||
7549a9ff22 | |||
6239c10579 | |||
78e75cffdb | |||
2c1ae783e3 | |||
8b6e464644 | |||
003eb1efc2 | |||
90d58bbee3 | |||
025e5bdf1c | |||
31dfec89ee | |||
9b53a3b0c9 | |||
03c4a4eae0 | |||
d822e0edd8 | |||
b326507417 | |||
414f81b32f | |||
bffdab1423 | |||
0494a9058d | |||
ef9a2c7190 | |||
7e01947da9 | |||
7bb805bffd | |||
ef9510e731 | |||
6e0f6d1d79 | |||
8feea380a4 | |||
7071600c3f | |||
806c6fbc2b | |||
90cbd7ab23 | |||
0ec0f216e2 | |||
32c491ae84 | |||
3fa45f9744 | |||
69f8710f31 | |||
824be11013 | |||
6e96a554ff | |||
3bbb09e813 | |||
cecf18f5a0 | |||
7014ded7f0 | |||
45d3f93192 | |||
2bb75da017 | |||
7de882338d | |||
3389ca18f7 | |||
b7be59bc6e | |||
ec1732088a | |||
302e5f22ce | |||
c7502e6a04 | |||
8881ef3af4 | |||
f5914a8d34 | |||
d6f0147339 | |||
cf5b1180a9 | |||
f0a23bcb47 | |||
e777c4c991 | |||
f8a70faf28 | |||
dde909457d | |||
2d26395d73 | |||
5f70669a84 | |||
add1712b7d | |||
566f205dc9 | |||
bd33e1f9c2 | |||
b10163575f | |||
c7d8843f06 | |||
b52748a93b | |||
8d49a80e79 | |||
cd677deec0 | |||
2c9dc5199c | |||
eaeb07db69 | |||
d0bafd5bd9 | |||
4bedbc4b25 | |||
8300fc8ca9 | |||
f16480092b | |||
c0b64580f9 | |||
b397fe33b7 | |||
8655d20234 | |||
96a3c05497 | |||
bed6539d5d | |||
6e867041e8 | |||
13394811bb | |||
a19579d4fd | |||
7e817106b1 | |||
1be499f242 | |||
527927da82 | |||
bcf9a1586a | |||
c714d24dfa | |||
a603d5bd3a | |||
32a32224ca | |||
0162224b7f |
3
.gitignore
vendored
@ -68,3 +68,6 @@
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
.flutter-plugins-dependencies
|
||||
|
||||
|
@ -33,7 +33,15 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 30
|
||||
|
||||
compileOptions {
|
||||
|
||||
// Required to use WebRTC
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
@ -42,11 +50,11 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "org.communiquons.comunic"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
@ -63,6 +71,10 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +89,10 @@ android {
|
||||
applicationId "org.communiquons.beta"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
@ -85,6 +101,6 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
3
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
## Flutter WebRTC
|
||||
-keep class com.cloudwebrtc.webrtc.** { *; }
|
||||
-keep class org.webrtc.** { *; }
|
BIN
android/app/src/beta/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB |
17
android/app/src/beta/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="0.33269298"
|
||||
android:scaleY="0.33269298"
|
||||
android:translateX="35.369194"
|
||||
android:translateY="20.2176">
|
||||
<group android:translateY="158.50157">
|
||||
<path android:pathData="M79,-42L107,-33.578125Q104.1875,-21.8125,98.15625,-13.921875Q92.140625,-6.03125,83.1875,-2.015625Q74.25,2,60.4375,2Q43.671875,2,33.046875,-2.890625Q22.421875,-7.796875,14.703125,-20.140625Q7,-32.484375,7,-51.75Q7,-77.421875,20.5625,-91.203125Q34.140625,-105,58.953125,-105Q78.375,-105,89.484375,-97.140625Q100.59375,-89.296875,106,-73.046875L78,-67Q76.53125,-71.546875,74.921875,-73.65625Q72.265625,-77.1875,68.421875,-79.09375Q64.578125,-81,59.828125,-81Q49.0625,-81,43.328125,-72.265625Q39,-65.796875,39,-51.921875Q39,-34.75,44.140625,-28.375Q49.296875,-22,58.625,-22Q67.65625,-22,72.28125,-27.125Q76.90625,-32.25,79,-42Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
16
android/app/src/beta/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#3c3f40" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@drawable/splash_icon" />
|
||||
</item>
|
||||
</layer-list>
|
BIN
android/app/src/beta/res/drawable/splash_icon.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
android/app/src/beta/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/beta/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
android/app/src/beta/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/beta/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
android/app/src/beta/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
android/app/src/beta/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
android/app/src/beta/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/beta/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
android/app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/beta/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#3C3F41</color>
|
||||
</resources>
|
@ -5,17 +5,31 @@
|
||||
<!-- Internet connection is required to access to the API -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- These permissions are required to make video calls (WebRTC) -->
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="Comunic"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@drawable/ic_app_rounded"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
@ -27,9 +41,18 @@
|
||||
until Flutter renders its first frame. It can be removed if
|
||||
there is no splash screen (such as the default splash screen
|
||||
defined in @style/LaunchTheme). -->
|
||||
|
||||
<meta-data
|
||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||
android:value="true" />
|
||||
android:name="io.flutter.embedding.android.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>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
BIN
android/app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 12 KiB |
@ -1,13 +1,6 @@
|
||||
package org.communiquons.comunic;
|
||||
|
||||
import android.os.Bundle;
|
||||
import io.flutter.app.FlutterActivity;
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
GeneratedPluginRegistrant.registerWith(this);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 10 KiB |
17
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="0.33269298"
|
||||
android:scaleY="0.33269298"
|
||||
android:translateX="35.369194"
|
||||
android:translateY="20.2176">
|
||||
<group android:translateY="158.50157">
|
||||
<path android:pathData="M79,-42L107,-33.578125Q104.1875,-21.8125,98.15625,-13.921875Q92.140625,-6.03125,83.1875,-2.015625Q74.25,2,60.4375,2Q43.671875,2,33.046875,-2.890625Q22.421875,-7.796875,14.703125,-20.140625Q7,-32.484375,7,-51.75Q7,-77.421875,20.5625,-91.203125Q34.140625,-105,58.953125,-105Q78.375,-105,89.484375,-97.140625Q100.59375,-89.296875,106,-73.046875L78,-67Q76.53125,-71.546875,74.921875,-73.65625Q72.265625,-77.1875,68.421875,-79.09375Q64.578125,-81,59.828125,-81Q49.0625,-81,43.328125,-72.265625Q39,-65.796875,39,-51.921875Q39,-34.75,44.140625,-28.375Q49.296875,-22,58.625,-22Q67.65625,-22,72.28125,-27.125Q76.90625,-32.25,79,-42Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
@ -1,12 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#1C227E" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
android:src="@drawable/splash_icon" />
|
||||
</item>
|
||||
</layer-list>
|
||||
|
BIN
android/app/src/main/res/drawable/splash_icon.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 2.0 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#1BA1E2</color>
|
||||
</resources>
|
@ -5,4 +5,9 @@
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
classpath 'com.android.tools.build:gradle:3.5.4'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
#Tue Mar 24 12:27:30 CET 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
1
android/settings_aar.gradle
Normal file
@ -0,0 +1 @@
|
||||
include ':app'
|
3
assets/langs.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"fr_FR": "fr"
|
||||
}
|
85
assets/langs/de.json
Normal file
@ -0,0 +1,85 @@
|
||||
{
|
||||
"%days%d": "%days% T",
|
||||
"%hours% h": "%hours% h",
|
||||
"%mins% min": "%mins% Min",
|
||||
"%months% months": "%months% Monate",
|
||||
"%num% likes": "%num% Personen gefällt das",
|
||||
"%num% members": "%num% Mitglieder",
|
||||
"%secs%s": "%sec% s",
|
||||
"%years% years": "%years% Jahre",
|
||||
"1 Like": "1 Person gefällt das",
|
||||
"1 member": "1 Mitglied",
|
||||
"1 month": "1 Monat",
|
||||
"1 year": "1 Jahr",
|
||||
"A network error occured!": "Ein Netzfelher ist augetreten!",
|
||||
"Accept": "Annehmen",
|
||||
"Account created": "Account geschafft",
|
||||
"An account is already associated to this email address!": "Diese E-Mail-Adresse wird schon mit einem Konto verbindet!",
|
||||
"An error occured while creating your account. Please try again.": "Während der Schaffung des Kontos ist ein Felher aufgetreten. Versuchen Sie bitte noch einmal.",
|
||||
"App settings": "Anwendungseinstellungen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Confirm": "Bestätigen",
|
||||
"Confirm operation": "Den Einstatz bestätigen",
|
||||
"Confirm your password": "Das Passwort bestätigen",
|
||||
"Could not get conversation information!": "Die Nachrichten des Gesprächs haben nicht wiedererlangt werden gekannt!",
|
||||
"Create a conversation": "Ein Gespräch schaffen",
|
||||
"Create an account": "Ein Konto schaffen",
|
||||
"Delete": "Löschen",
|
||||
"Delete friend": "Einen Freund löschen",
|
||||
"Do you really want to sign out from the application ?": "Wollen Sie tatsächlich ",
|
||||
"Email address": "E-Mail-Adresse",
|
||||
"Enable dark theme": "Das dunkle Thema anwenden",
|
||||
"Error": "Fehler",
|
||||
"Error while creating your account": "Während der Schaffung ihres Kontos ist ein Felher aufgetreten.",
|
||||
"First name": "Vorname",
|
||||
"Follow": "Folgen",
|
||||
"Follow conversation": "Das Gespräch folgen",
|
||||
"Following": "Gefolgt",
|
||||
"Friends": "Freunden",
|
||||
"Friends of %name%": "Freunden von %name%",
|
||||
"Friends only": "Nur für die Freunde",
|
||||
"Group members only": "Nur für die Mitglieder",
|
||||
"I have read and accepted the Terms Of Service.": "Ich habe die Nutzungsbedingungen gelesen und angenommen.",
|
||||
"Invalid credentials!": "Ungültigen Anmeldedaten!",
|
||||
"Invalid email address!": "Ungültiger E-Mail-Adresse!",
|
||||
"Invalid first name!": "Ungültige Vorname!",
|
||||
"Invalid last name!": "Ungültiger Nachname!",
|
||||
"Invalid password!": "Ungültiges Passwort!",
|
||||
"Last name": "Nachname",
|
||||
"Like": "Gefällt mir",
|
||||
"Loading": "Laden ausstehend",
|
||||
"Login": "Einloggen",
|
||||
"Me only": "Nur für mich",
|
||||
"Menu": "Menü",
|
||||
"New message": "Neue Meldung",
|
||||
"Newest": "Neuest",
|
||||
"OK": "OK",
|
||||
"Ok": "Ok",
|
||||
"Online": "Angeschlossen",
|
||||
"PDF": "PDF",
|
||||
"Password": "Passwort",
|
||||
"Please sign into your Comunic account: ": "Einloggen Sie Sie bitte in ihrem Comunic-Konto",
|
||||
"Post": "Beitrag",
|
||||
"Public": "Publik",
|
||||
"Reject": "Absagen",
|
||||
"Remove": "Löschen",
|
||||
"Retry": "Versuchen noch einmal",
|
||||
"Send": "Senden",
|
||||
"Send a message": "Eine Meldung senden",
|
||||
"Sign in": "Sich einloggen",
|
||||
"Sign out": "Log-out",
|
||||
"The password and its confirmation do not match!": "Das Passwort und seine Bestätigung vergleichen nicht!",
|
||||
"This account is private.": "Dieses Konto ist privat.",
|
||||
"Too many accounts have been created from this IP address for now. Please try again later.": "Zu viele Konten sind bisher mit dieser IP-Adresse geshafft worden. Versuchen Sie bitte später noch einmal.",
|
||||
"Too many unsuccessfull login attempts! Please try again later...": "Zu vielen erfolglose Anmeldeversuche! Versuchen Sie bitte später noch einmal...",
|
||||
"Try again": "Noch einmal versuchen",
|
||||
"Update message": "Die Meldung ändern",
|
||||
"You must accept the Terms Of Service to continue.": "Sie müssen die Nutzungsbedingungen lesen, um weiterzugehen.",
|
||||
"You will need to restart the application to apply changes": "Sie müssen die Anwendung neu starten, um die Veränderungen anzusetzen",
|
||||
"Your account has been successfully created. You can now login to start to use it.": "Ihr Konto ist erfolgreich geschafft worden. Sie können Sie jetzt anmelden und es verwalten.",
|
||||
"cancel": "abbrechen",
|
||||
"created a new post": "hat einen Beitrag geschafft",
|
||||
"delete": "bestätigen",
|
||||
"https://www.youtube.com/watch/?v=": "https://www.youtube.com/watch/?v=",
|
||||
"posted a comment": "hat einen Kommentar gepostet"
|
||||
}
|
452
assets/langs/fr.json
Normal file
@ -0,0 +1,452 @@
|
||||
{
|
||||
"%days% Days %hours% Hours %minutes% Minutes %seconds% Seconds": "\"%days% Jours %hours% Heures %minutes% Minutes %seconds% Secondes\"",
|
||||
"%days%d": "%days% j",
|
||||
"%hours% h": "%hours% h",
|
||||
"%mins% min": "%mins% min",
|
||||
"%months% months": "%months% mois",
|
||||
"%num% like": "%num% J'aime",
|
||||
"%num% likes": "%num% personnes aiment",
|
||||
"%num% members": "%num% membres",
|
||||
"%secs%s": "%secs% s",
|
||||
"%years% years": "%years% ans",
|
||||
"1 Like": "1 personne aime",
|
||||
"1 member": "1 membre",
|
||||
"1 month": "1 mois",
|
||||
"1 year": "1 an",
|
||||
":yourShortcut:": ":votreRaccourcis:",
|
||||
"A network error occured!": "Une erreur de réseau s'est produite !",
|
||||
"A registration is required to access this group page.": "Une inscription est nécessaire pour accéder à cette page de groupe !",
|
||||
"About this application": "A propos de cette application",
|
||||
"Accept": "Accepter",
|
||||
"Accept request": "Accepter la demande",
|
||||
"Access restrictions": "Restrictions d'accès",
|
||||
"Accessible to everyone, including non-Comunic users": "Accessible par tout le monde, y compris les personnes extérieures à Comunic",
|
||||
"Account created": "Compte créé",
|
||||
"Account image": "Image de compte",
|
||||
"Account image visibility": "Visibilité de l'image de compte",
|
||||
"Account image visiblity": "Visibilité de votre image de compte",
|
||||
"Add": "Ajouter",
|
||||
"Add image": "Ajouter une image",
|
||||
"Add member": "Ajouter un membre",
|
||||
"Add new emoji": "Ajouter un nouvel émoticon",
|
||||
"Administrator": "Administrateur",
|
||||
"All members": "Tous les membres",
|
||||
"All the members of the group can create posts on the group": "Tous les membres du groupe peuvent créer des posts",
|
||||
"Allow all members of the conversation to add users": "Autoriser tous les membres de la conversation d'ajouter des utilisateurs",
|
||||
"Allow comments on your page": "Autoriser les commentaires sur votre page",
|
||||
"Allow comunic to send emails": "Autoriser Comunic à envoyer des mails",
|
||||
"Allow posts from your friends on your page": "Autoriser les posts de vos amis sur votre page",
|
||||
"Allow users to create new choices": "Autoriser les utilisateurs à créer de nouveaux choix",
|
||||
"An account is already associated to this email address!": "Un compte est déjà associer à cette addresse e-mail !",
|
||||
"An error occured while creating your account. Please try again.": "Une erreur s'est produite lors de la création du compte. Veuillez ré-essayer.",
|
||||
"An error occurred while checking your options !": "Erreur lors de la récupération de vos options de récupération !",
|
||||
"An error occurred while checking your recovery options !": "Erreur lors de la récupération de vos options de récupération !",
|
||||
"Answer %num%": "Réponse %num%",
|
||||
"Answer 1": "Réponse 1",
|
||||
"Answer 2": "Réponse 2",
|
||||
"Answer your security questions": "Répondre à vos questions de sécurité",
|
||||
"App settings": "Paramètres de l'application",
|
||||
"Appearance": "Apparence",
|
||||
"Application settings": "Paramètres de l'application",
|
||||
"Are you sure do you want to remove this friend from your list of friends ? A friendship request will have to be sent to get this user back to your list!": "Voulez-vous vraiment supprimer cet ami de votre liste d'amis ? Il faudra une demande d'ami pour réintégrer cet utilisateur à votre liste !",
|
||||
"Block the creation of new responses": "Bloquer la création de nouvelles réponses",
|
||||
"Camera": "Caméra",
|
||||
"Can access to all group posts": "Peut accéder à tous les posts du groupe",
|
||||
"Can always create posts, invite users and respond to membership request": "Peut toujours envoyer des posts, inviter des utilisateurs et répondre aux demande d'inscription au groupe",
|
||||
"Can change members privileges and group settings": "Peut changer les privilèges des utilisateurs et les paramètres du groupe",
|
||||
"Cancel": "Annuler",
|
||||
"Cancel request": "Annuler la demande",
|
||||
"Cancel response to survey": "Annuler la réponse au sondage",
|
||||
"Change account image visibility": "Changer la visibilité de l'image de compte",
|
||||
"Change level": "Changer le niveau",
|
||||
"Change password": "Changer le mot de passe",
|
||||
"Change your password": "Changer de mot de passe",
|
||||
"Change your security questions": "Changer les questions de sécurité",
|
||||
"Checking availability...": "Vérification de la disponibilité...",
|
||||
"Choose a new password": "Choisir un nouveau mot de passe",
|
||||
"Choose a user": "Choisir un utilisateur",
|
||||
"Choose a virtual directory": "Choisir un répertoire virtuel",
|
||||
"Choose an image": "Choisir une image",
|
||||
"Closed registration": "Inscription fermée",
|
||||
"Comunic": "Comunic",
|
||||
"Comunic is a free and OpenSource social network that respect your privacy.": "Comunic est un réseau social libre qui respecte votre vie privée.",
|
||||
"Comunic users": "Les utilisateurs de Comunic",
|
||||
"Configure the main settings of your account": "Configurer les paramètres principaux de votre compte",
|
||||
"Confirm": "Confirmer",
|
||||
"Confirm deletion": "Confirmer la suppression",
|
||||
"Confirm operation": "Confirmer l'opération",
|
||||
"Confirm you new password": "Confirmer le mot de passe",
|
||||
"Confirm your password": "Confirmer le mot de passe",
|
||||
"Congratulations! Your password has now been successfully changed!": "Félicitations ! Votre mot de passe a bien été changé !",
|
||||
"Connected users": "Utilisateurs connectés",
|
||||
"Conversation members": "Membres de la conversation",
|
||||
"Conversation name (optionnal)": "Nom de la conversation (optionnel)",
|
||||
"Conversations": "Conversations",
|
||||
"Could not block the creation of new choices!": "Erreur lors du bloquage de la création de nouveaux choix !",
|
||||
"Could not cancel invitation!": "Erreur lors de l'annulation de l'invitation !",
|
||||
"Could not cancel your membership request!": "Erreur lors de l'annulation de votre demande à rejoindre ce groupe !",
|
||||
"Could not cancel your response to the survey !": "Impossible d'annuler votre réponse au sondage !",
|
||||
"Could not change group membership level!": "Erreur lors du changement du niveau d'inscription au groupe !",
|
||||
"Could not change your password!": "Erreur lors du changement de votre mot de passe !",
|
||||
"Could not connect to a remote peer!": "Erreur lors de la connexion à un membre de l'appel !",
|
||||
"Could not connect to server!": "Echec de la connexion au serveur",
|
||||
"Could not create a new choice for this survey!": "Erreur lors de la création d'un nouveau choix pour ce sondage !",
|
||||
"Could not create a new group!": "Erreur lors de la création du groupe !",
|
||||
"Could not create comment!": "Impossible de créer le commentaire ! ",
|
||||
"Could not create post !": "Impossible de créer le post !",
|
||||
"Could not create the conversation!": "Impossible de créer la conversation !",
|
||||
"Could not delete all your notifications!": "Erreur lors de la suppression de vos notifications !",
|
||||
"Could not delete conversation message!": "Impossible de supprimer le message de la conversation !",
|
||||
"Could not delete custom emoji!": "Erreur lors de la suppression de l'émoticon personnalisé !",
|
||||
"Could not delete group logo!": "Erreur lors de la suppression du logo du groupe !",
|
||||
"Could not delete the comment!": "Impossible de supprimer le commentaire !",
|
||||
"Could not delete the conversation!": "Impossible de supprimer la conversation !",
|
||||
"Could not delete the group": "Erreur lors de la suppression du groupe !",
|
||||
"Could not delete the post!": "Impossible de supprimer le post !",
|
||||
"Could not delete this person from your friends list!": "Impossible de supprimer cet utilisateur de votre liste d'amis !",
|
||||
"Could not delete user account image!": "Erreur lors de la suppression de votre image de compte !",
|
||||
"Could not delete your account!": "Erreur lors de la suppression de votre compte !",
|
||||
"Could not disconnect you from all your devices!": "Erreur lors de la déconnexion de tous vos appareils !",
|
||||
"Could not find a private conversation!": "Impossible de trouver une conversation privée !",
|
||||
"Could not find related resource!": "La ressource liée n'a pas été trouvée !",
|
||||
"Could not generate new random logo!": "Erreur lors de la génération d'un logo aléatoire !",
|
||||
"Could not get account image settings!": "Erreur lors de la récupération des paramètres de l'image de compte !",
|
||||
"Could not get basic group information!": "Erreur lors de la récupération des informations de base du groupe !",
|
||||
"Could not get conversation information!": "Impossible de récupérer les informations de la conversation !",
|
||||
"Could not get group settings!": "Erreur lors de la récupération des paramètres du groupe !",
|
||||
"Could not get the list of friends of %name% !": "Impossible de récupérer la liste d'amis de %name% !",
|
||||
"Could not get the list of friends of this user !": "Erreur lors de la récupération de la liste d'amis de cet utilisateur !",
|
||||
"Could not get the list of notifications!": "Impossible de récupérer la liste des notifications !",
|
||||
"Could not get the list of posts !": "Impossible de récupérer la liste des posts !",
|
||||
"Could not get user information!": "Impossible de récupérer les informations de l'utilisateur !",
|
||||
"Could not initialize call!": "Erreur lors de l'initialisation de l'appel !",
|
||||
"Could not invite a user!": "Erreur lors de l'envoi de l'invitation pour l'utilisateur !",
|
||||
"Could not load conversation information!": "Erreur lors de la récupération des informations sur la conversation !",
|
||||
"Could not load friendship information!": "Erreur lors de la récupération des informations sur cet ami !",
|
||||
"Could not load general settings!": "Erreur lors du chargement des paramètres généraux !",
|
||||
"Could not load information about the conversation": "Impossible de récupérer les informations sur la conversation",
|
||||
"Could not load information about the group!": "Erreur lors du chargement des informations du groupe !",
|
||||
"Could not load settings!": "Erreur lors du chargement des paramètres",
|
||||
"Could not load the list of groups!": "Erreur lors du chargement de la liste des groupes !",
|
||||
"Could not load the list of members of this conversation!": "Erreur lors de la récupération des membres de cette conversation !",
|
||||
"Could not load the list of members of this group!": "Erreur lors de la récupération de la liste des membres de ce groupe !",
|
||||
"Could not load the list of messages!": "Impossible de charger la liste des messages !",
|
||||
"Could not load the list of unread conversations!": "Erreur lors de la récupération de la liste des conversations non lues !",
|
||||
"Could not load your list of friends!": "Impossible de charger votre liste d'amis !",
|
||||
"Could not load your security questions!": "Erreur lors de la récupération de vos questions de sécurité !",
|
||||
"Could not peform search!": "Erreur lors de l'exécution de la recherche !",
|
||||
"Could not perform search!": "Erreur lors de l'exécution de la recherche !",
|
||||
"Could not pick a PDF!": "Erreur lors du choix d'un PDF",
|
||||
"Could not refresh user information!": "Erreur lors de la récupération des informations de l'utilisateur !",
|
||||
"Could not remove this membership!": "Erreur lors de la suppression de l'inscription !",
|
||||
"Could not remove your membership to this group!": "Erreur lors de la suppresison de votre inscription à ce groupe !",
|
||||
"Could not respond to friendship request!": "Impossible de répondre à la demande d'ami !",
|
||||
"Could not respond to membership request!": "Erreur lors de la réponse à la demande d'inscription !",
|
||||
"Could not respond to your invitation!": "Erreur lors de la réponse à l'invitation !",
|
||||
"Could not retrieve created comment!": "Impossible d'afficher le commentaire !",
|
||||
"Could not retrieve the list of conversations!": "Impossible d'afficher la liste des conversations !",
|
||||
"Could not search virtual directory!": "Erreur lors de la recherche du répertoire virtuel !",
|
||||
"Could not send message!": "Impossible d'envoyer le message !",
|
||||
"Could not send your membership request!": "Erreur lors de l'envoi de votre demande d'inscription !",
|
||||
"Could not send your response to the survey!": "Impossible d'envoyer votre réponse au sondage !",
|
||||
"Could not start streaming!": "Erreur lors du démarrage du flux !",
|
||||
"Could not update account image visibility level!": "Erreur lors de la mise à jour de la visibilité de votre image de compte !",
|
||||
"Could not update comment content!": "Impossible de modifier le contenu du mcommentaire !",
|
||||
"Could not update following status!": "Impossible de modifier le statut de suivi !",
|
||||
"Could not update general settings!": "Erreur lors de la mise à jour des paramètres généraux !",
|
||||
"Could not update group membership!": "Erreur lors de la mise à jour de l'inscription !",
|
||||
"Could not update group settings!": "Erreur lors de la mise à jour des paramètres du groupe !",
|
||||
"Could not update message content!": "Impossible de modifier le contenu du message !",
|
||||
"Could not update password!": "Erreur lors de la mise à jour de votre mot de passe !",
|
||||
"Could not update post content!": "Impossible de modifier le contenu du post !",
|
||||
"Could not update post visibility!": "Impossible de modifier la visibilité du post !",
|
||||
"Could not update security questions!": "Erreur lors de la mise à jour des questions de sécurité !",
|
||||
"Could not update the conversation!": "Impossible de modifier la conversation !",
|
||||
"Could not update your membership!": "Impossible de modifier votre adhésion !",
|
||||
"Could not upload emoji!": "Erreur lors de l'upload de l'émoticon !",
|
||||
"Could not upload new logo!": "Erreur lors de l'envoi du nouveau logo !",
|
||||
"Could not upload your account image!": "Erreur lors de l'envoi de votre image de compte !",
|
||||
"Could not upload your generated account image!": "Erreur lors de l'envoi de votre nouvelle image de compte !",
|
||||
"Could not validate these security answers!": "Erreur lors de la validation de ces réponses de sécurité !",
|
||||
"Could not validate your password reset token! Maybe it has expired now...": "Impossible de vérifier votre clé de changement de mot de passe ! Peut-être a-t-elle expiré...",
|
||||
"Create a conversation": "Créer une conversation",
|
||||
"Create a new choice": "Créer un nouveau choix",
|
||||
"Create a new post...": "Créer un nouveau post...",
|
||||
"Create an account": "Créer un compte",
|
||||
"Create the conversation": "Créer la conversation",
|
||||
"Current account image": "Image de compte actuelle",
|
||||
"Current choices:": "Choix actuels :",
|
||||
"Current level: %level%": "Niveau actuel : %level%",
|
||||
"Current logo": "Logo actuel",
|
||||
"Custom emojis": "Emoticons personnalisés",
|
||||
"Customize your account image": "Personalisez votre image de compte",
|
||||
"Danger zone": "Zone de danger",
|
||||
"Debug features": "Fonctionnalités de développement",
|
||||
"Delete": "Supprimer",
|
||||
"Delete account image": "Supprimer l'image de compte",
|
||||
"Delete comment": "Supprimer le commentaire",
|
||||
"Delete conversation": "Supprimer la conversation",
|
||||
"Delete friend": "Supprimer un ami",
|
||||
"Delete group": "Supprimer le groupe",
|
||||
"Delete logo": "Supprimer le logo",
|
||||
"Delete your account": "Supprimer votre compte",
|
||||
"Disconnect all your devices": "Déconnecter tous vos appareils",
|
||||
"Disconnect all your devices from Comunic, including the current one. Use this option if one of the device you use for Comunic was stolen.": "Déconnecte tous vos appareils de Comunic, en incluant l'appareil actuel. Nous vous recommandons d'utiliser cette option si vous avez des raisons de penser que l'un des appareils que vous utiliser pour accéder à Comunic a été volé.",
|
||||
"Do you really want to block new choices creation?": "Voulez-vous vraiment bloquer la création de nouveaux choix ?",
|
||||
"Do you really want to block new responses ?": "Voulez-vous vraiment bloquer la création de nouveaux choix ?",
|
||||
"Do you really want to cancel your response to this survey ?": "Voulez-vous vraiment annuler votre réponse au sondage ?",
|
||||
"Do you really want to delete all your notifications?": "Voulez-vous vraiment supprimer toute vos notifications ?",
|
||||
"Do you really want to delete the logo of this group ?": "Voulez-vous vraiment supprimer le logo de ce groupe ?",
|
||||
"Do you really want to delete this comment ?": "Voulez-vous vraiment supprimer ce commentaire ?",
|
||||
"Do you really want to delete this custom emoji ?": "Voulez-vous vraiment supprimer cet émoticon personnalisé ?",
|
||||
"Do you really want to delete this group ? All the posts related to it will be permanently deleted!": "Voulez-vous vraiment supprimer ce groupe ? Tous les posts s'y rapportant seront également supprimés !",
|
||||
"Do you really want to delete this group membership ?": "Voulez-vous vraiment quitter ce groupe ?",
|
||||
"Do you really want to delete this message ? The operation can not be cancelled !": "Voulez-vous vraiment supprimer ce message ? Cette opération est irréversible !",
|
||||
"Do you really want to delete this post ? The operation can not be reverted !": "Voulez-vous vraiment supprimer ce post ? Cette opération est irréversible !",
|
||||
"Do you really want to delete your account image ?": "Voulez-vous vraiment supprimer votre image de compte ?",
|
||||
"Do you really want to delete your account? This operation CAN NOT be reverted!": "Voulez-vous vraiment supprimer votre compte ? Cette opération NE PEUT PAS être annulée !",
|
||||
"Do you really want to disconnect all your devices from Comunic ?": "Voulez-vous vraiment déconnecter tous vos appareils de Comunic ?",
|
||||
"Do you really want to leave this call ?": "Voulez-vous vraiment quitter cet appel ?",
|
||||
"Do you really want to reject this friendship request?": "Voulez-vous vraiment rejeter cette demande d'amis ?",
|
||||
"Do you really want to reject this invitation?": "Voulez-vous vraiment refuser cette invitation ?",
|
||||
"Do you really want to remove this conversation from your list of conversations ? If you are the owner of this conversation, it will be completely deleted!": "Voulez-vous vraiment supprimer la conversation de votre liste ? Si vous êtes le créateur de cette conversation, elle sera définitivement supprimée !",
|
||||
"Do you really want to remove this membership ?": "Voulez-vous vraiment supprimer cette inscription ?",
|
||||
"Do you really want to sign out from the application ?": "Voulez-vous vraiment vous déconnecter de l'application ?",
|
||||
"Do you want to unselected currently selected image ?": "Voulez-vous désélectionner l'image ?",
|
||||
"Email address": "Adresse e-mail",
|
||||
"Email address...": "Adresse mail...",
|
||||
"Enable dark theme": "Activer le thème sombre",
|
||||
"Error": "Erreur",
|
||||
"Error while creating your account": "Une erreur s'est produite lors de la création de votre compte.",
|
||||
"Error while processing new signal!": "Erreur lors du traitement d'un signal !",
|
||||
"Everyone": "Tout le monde",
|
||||
"Everyone can choose to join the group without moderator approval": "Tout le monde peut rejoindre le groupe, sans l'approbation d'un modérateur",
|
||||
"Everyone can request a membership, but a moderator review the request": "Tout le monde peut demander à rejoindre le groupe, mais un modérateur doit accepter les demandes",
|
||||
"First name": "Prénom",
|
||||
"Follow": "Suivre",
|
||||
"Follow conversation": "Suivre la conversation",
|
||||
"Following": "Suivi",
|
||||
"Force mobile mode": "Forcer l'utilisation du mode mobile",
|
||||
"Force the smartphone mode of the application to be used, even when tablet mode could be used.": "Forcer l'utilisation du mode smartphone de l'application, même lorsque le mode tablette est disponible.",
|
||||
"Form can not be submitted at this point!": "Impossible de soumettre le formulaire à ce stade !",
|
||||
"Free social network that respect your privacy": "Réseau sociale libre qui respecte votre vie privée",
|
||||
"Friends": "Amis",
|
||||
"Friends of %name%": "Amis de %name%",
|
||||
"Friends only": "Amis seulement",
|
||||
"General": "Général",
|
||||
"General information": "Informations générales",
|
||||
"General settings": "Paramètres généraux",
|
||||
"Generate a new random logo": "Générer un logo aléatoire",
|
||||
"Generate a random account image": "Générer une image de compte aléatoire",
|
||||
"Group": "Groupe",
|
||||
"Group ID": "Identifiant du gorupe",
|
||||
"Group URL (optional)": "URL du groupe (optionnelle)",
|
||||
"Group description (optional)": "Description du groupe (optionnelle)",
|
||||
"Group information & public posts are available to everyone.": "Les informations du groupe ainsi que ses posts public sont accessibles à tous",
|
||||
"Group logo": "Logo du groupe",
|
||||
"Group members": "Membres du groupe",
|
||||
"Group members only": "Membres du groupe seulement",
|
||||
"Group name": "Nom du groupe",
|
||||
"Group registration level": "Inscription au groupe",
|
||||
"Group settings": "Paramètres du groupe",
|
||||
"Group visibility": "Visibilité du groupe",
|
||||
"Groups": "Groupes",
|
||||
"Here are your options to reset your account:": "Voici les options à votre disposition pour réinitialiser votre compte :",
|
||||
"Here you can make actions to protect your privacy": "Agissez pour protéger votre vie privée",
|
||||
"I have read and accepted the Terms Of Service.": "J'ai lu et accepté les Conditions d'utilisation.",
|
||||
"Image": "Image",
|
||||
"Image gallery": "Galerie",
|
||||
"Input YouTube URL": "Entrez l'URL de la vidéo YouTube",
|
||||
"Invalid URL!": "URL invalide !",
|
||||
"Invalid YouTube link!": "Lien YouTube invalide !",
|
||||
"Invalid credentials!": "Identifiants invalides !",
|
||||
"Invalid email address!": "Adresse e-mail invalide !",
|
||||
"Invalid first name!": "Prénom invalide !",
|
||||
"Invalid last name!": "Nom invalide !",
|
||||
"Invalid password!": "Mot de passe invalide !",
|
||||
"Invalid shortcut!": "Raccourcis invalide !",
|
||||
"Invalid value!": "Valeur invalide !",
|
||||
"Invited": "Invité",
|
||||
"Last name": "Nom",
|
||||
"Learn more about us": "En savoir plus sur nous",
|
||||
"Let us ask you one last time. Do you really want to delete your account? If you decide to do so, your data will be permanently removed from our servers, so we will not be able to recover your account. If you decide to proceed, the deletion process will start immediatly and you will automatically get disconnected from your account.": "Laissez-nous vous demander une dernière fois. Voulez-vous vraiment supprimer votre compte ? Si vous décidez de continuer, les données liées à votre compte vont être supprimées de manière permanente de nos serveurs, et nous ne seront pas en mesure de les restaurer. Si vous décidez de poursuivre, le processus de supprimer vas débuter immédiatement et vous serez automatiquement déconnecté de votre compte.",
|
||||
"Like": "J'aime",
|
||||
"Loading": "Chargement",
|
||||
"Loading...": "Chargement...",
|
||||
"Login": "Connexion",
|
||||
"Main account information": "Informations principales du compte",
|
||||
"Make your friends list public": "Rendre votre liste d'amis publique",
|
||||
"Manage local application settings": "Paramètres locaux de l'application",
|
||||
"Manage security options of your account": "Accédez aux options de sécurité pour votre compte",
|
||||
"Me only": "Moi seulement",
|
||||
"Member": "Membre",
|
||||
"Member for %t%": "Membre depuis %t%",
|
||||
"Members": "Membres",
|
||||
"Membership": "Inscription",
|
||||
"Menu": "Menu",
|
||||
"Message rejected by the server!": "Message rejeté par le serveur !",
|
||||
"Moderated registration": "Inscription modérée",
|
||||
"Moderator": "Modérateur",
|
||||
"Moderators only": "Modérateurs uniquement",
|
||||
"My Page": "Ma page",
|
||||
"My friends": "Mes amis",
|
||||
"My friends only": "Mes amis uniquement",
|
||||
"Name of the group": "Nom du groupe",
|
||||
"Name of the group to create": "Nom du groupe à créer",
|
||||
"New choice": "Nouveau choix",
|
||||
"New choice...": "Nouveau choix...",
|
||||
"New comment...": "Nouveau commentaire...",
|
||||
"New content...": "Nouveau contenu...",
|
||||
"New content:": "Nouveau contenu :",
|
||||
"New membership level": "Nouveau niveau d'appartenance au groupe",
|
||||
"New message": "Nouveau message",
|
||||
"New password": "Nouveau mot de passe",
|
||||
"New survey": "Nouveau sondage",
|
||||
"Newest": "Plus récent",
|
||||
"Night mode": "Thème sombre",
|
||||
"No account image yet...": "Pas encore d'image de compte...",
|
||||
"No choice yet.": "Aucun choix pour le moment.",
|
||||
"No response yet to this survey.": "Pas encore de réponse à ce sondage",
|
||||
"Note": "Note",
|
||||
"Note: Your two questions and answers MUST be completed in order to be able to recover your account using your security questions!": "Note : Vos deux questions de sécurité DOIVENT être complétées pour que vous puissiez récupérer l'accès à votre compte depuis vos questions de sécurité !",
|
||||
"Notifications": "Notifications",
|
||||
"OK": "OK",
|
||||
"Ok": "Ok",
|
||||
"Ongoing call": "Appel en cours",
|
||||
"Online": "Connecté",
|
||||
"Only moderators and administrators of the group can create posts on it": "Seuls les modérateurs et les administrateurs du groupe peuvent créer des posts",
|
||||
"Open": "Ouvert",
|
||||
"Open group": "Groupe ouvert",
|
||||
"Open in full screen": "Ouvrir en plein écran",
|
||||
"Open registration": "Inscription ouverte",
|
||||
"Owner": "Propriétaire",
|
||||
"PDF": "PDF",
|
||||
"Page visibility": "Visibilité de la page",
|
||||
"Password": "Mot de passe",
|
||||
"Password forgotten": "Mot de passe oublié",
|
||||
"Password required": "Mot de passe requis",
|
||||
"Permanently delete your account and all data related to it.": "Supprimer de manière permanente votre compte et toute les données qui y sont rattachées",
|
||||
"Personal website URL (optional)": "Site web personnel (optionnel)",
|
||||
"Please answer now your security questions:": "Veuillez répondre à vos questions de sécurité :",
|
||||
"Please choose new account image visibility level:": "Veuillez choisir un nouveau niveau de visibilité pour votre image de compte :",
|
||||
"Please enter message content: ": "Veuillez entrer le contenu du message :",
|
||||
"Please enter new message content:": "Veuillez entrer le contenu du nouveau message :",
|
||||
"Please enter your email address to reset your password:": "Veuillez entrer votre adresse mail pour changer votre mot de passe :",
|
||||
"Please sign into your Comunic account: ": "Veuillez vous connecter à votre compte Comunic :",
|
||||
"Please specify the new choice for the survey": "Veuillez spécifier le nouveau choix pour ce sondage",
|
||||
"Post": "Post",
|
||||
"Post content": "Contenu du post",
|
||||
"Posts creation level": "Création de posts",
|
||||
"Privacy": "Vie privée",
|
||||
"Privacy settings": "Vie privée",
|
||||
"Private": "Privé",
|
||||
"Private conversation": "Conversation privée",
|
||||
"Private group": "Groupe privé",
|
||||
"Private, accessible only to your friends": "Privé, accessible uniquement par vous et vos amis",
|
||||
"Public": "Public",
|
||||
"Public note (optional)": "Note publique (optionnelle)",
|
||||
"Public, accessible to all Comunic members": "Publique, accessible par tous les membres de Comunic",
|
||||
"Question": "Question",
|
||||
"Question 1": "Question 1",
|
||||
"Question 2": "Question 2",
|
||||
"Reject": "Rejeter",
|
||||
"Reject request": "Rejeter la demande",
|
||||
"Remove": "Supprimer",
|
||||
"Remove selected image": "Supprimer l'image sélectionnée",
|
||||
"Replace image": "Remplacer l'image",
|
||||
"Request membership": "Demander de rejoindre le groupe",
|
||||
"Requested": "En attente",
|
||||
"Respond to survey": "Répondre au sondage",
|
||||
"Retry": "Ré-essayer",
|
||||
"Search": "Recherche",
|
||||
"Search a user, a group...": "Rechercher un utilisateur, un groupe...",
|
||||
"Search user...": "Rechercher un utilisateur...",
|
||||
"Search...": "Rechercher...",
|
||||
"Secrete group": "Groupe secret",
|
||||
"Security": "Sécurité",
|
||||
"Select new post visibility level": "Sélectionner la nouvelle visibilité du post",
|
||||
"Send": "Envoyer",
|
||||
"Send a message": "Envoyer un message",
|
||||
"Send request": "Envoyer la demande",
|
||||
"Send us an email to ask for help": "Envoyez-nous un mail pour demander notre aide",
|
||||
"Set your own emoticon shorcuts": "Définissez vos propres raccourcis d'émoticons",
|
||||
"Settings": "Paramètres",
|
||||
"Shortcut": "Raccourcis",
|
||||
"Show more comments": "Afficher plus de commentaires",
|
||||
"Show performances overlay": "Afficher les informations sur les performances",
|
||||
"Sign in": "Connexion",
|
||||
"Sign out": "Déconnexion",
|
||||
"Specified email address was not found!": "L'adresse mail spécifiée n'a pas été trouvée !",
|
||||
"Specify URL": "Spécifier l'URL",
|
||||
"Stop streaming": "Arrêter de partager ma vidéo & mon audio",
|
||||
"Submit": "Valider",
|
||||
"Switch camera": "Changer de caméra",
|
||||
"The group is accessible to accepted members only.": "Le groupe n'est accessible qu'à ses membres",
|
||||
"The group is visible only to invited members.": "Le groupe n'apparaît qu'à ses membres, et aux personnes invitées à le rejoindre.",
|
||||
"The only way to join the group is to be invited by a moderator": "Seul un modérateur peut inviter quelqu'un à rejoindre le groupe",
|
||||
"The password and its confirmation do not match!": "La confirmation ne correspond pas au mot de passe !",
|
||||
"The post has been successfully created!": "Le post a été créé avec succès !",
|
||||
"There is no message yet in this converation.": "Il y n'a pas encore de message dans cette conversation.",
|
||||
"There is no post to display here yet.": "Il n'y a pas encore de post à afficher.",
|
||||
"This account is private.": "Ce compte est privé.",
|
||||
"This kind of notification is not supported yet by this application.": "Ce type de notification n'est pas encore supportée par l'application.",
|
||||
"This password is not the same as the other one!": "Ce mot de passe est différent de l'autre",
|
||||
"This virtual directory is invalid / unvailable !": "Ce répertoire virtuel est invalide / indisponible !",
|
||||
"Too many accounts have been created from this IP address for now. Please try again later.": "Trop de comptes ont été créés avec cette addresse IP pour l'instant. Veuillez ré-essayer plus tard.",
|
||||
"Too many unsuccessfull login attempts! Please try again later...": "Trop de tentatives de connexion ont échoué. Veuillez ré-essayer plus tard...",
|
||||
"Try again": "Essayer à nouveau",
|
||||
"Unsafe value!": "Valeur non sûre !",
|
||||
"Update": "Modifier",
|
||||
"Update a conversation": "Modifier une conversation",
|
||||
"Update comment content": "Modifier le contenu du commentaire",
|
||||
"Update content": "Modifier le contenu",
|
||||
"Update message": "Modifier un message",
|
||||
"Update post content": "Modifier le contenu du post",
|
||||
"Update security questions": "Mise à jour des questions de sécurité",
|
||||
"Update the conversation": "Modifier la conversation",
|
||||
"Upload a new logo": "Envoyer un nouveau logo",
|
||||
"Upload an account image": "Envoyer une nouvelle image de compte",
|
||||
"Upload new account image": "Changer l'image de compte",
|
||||
"User ID": "Numéro d'utilisateur",
|
||||
"Virtual directory": "Répertoire virtuel",
|
||||
"Virtual directory (optional)": "Dossier virtuel (optionnel)",
|
||||
"Visitor": "Visiteur",
|
||||
"Website": "Site web",
|
||||
"You can choose a new password.": "Vous pouvez choisir un nouveau mot de passe.",
|
||||
"You can reach us at contact@communiquons.org": "Vous pouvez nous contacter à l'adresse contact@communiquons.org",
|
||||
"You can use this virtual directory.": "Vous pouvez utiliser ce répertoire virtuel.",
|
||||
"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 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 will need to restart the application to apply changes": "Vous aurez besoin de redémarrer l'application pour appliquer les changements",
|
||||
"YouTube movie": "Vidéo YouTube",
|
||||
"Your account has been successfully created. You can now login to start to use it.": "Votre compte a été créé avec succès. Vous pouvez à présent vous connecter et le gérer.",
|
||||
"Your account image is visible by everyone, including users external to Comunic.": "Votre image de compte est visible par tout le monde, ainsi que les personnes non connectées.",
|
||||
"Your account image is visible only by your friends.": "Votre image de compte n'est visible que par vos amis",
|
||||
"Your account image is visible only to connected Comunic users.": "Votre image de compte n'est accessible qu'aux personnes connectées.",
|
||||
"Your current password": "Mot de passe actuel",
|
||||
"Your friends list": "Votre liste d'amis",
|
||||
"Your new password": "Votre nouveau mot de passe",
|
||||
"Your page settings": "Paramètres de votre page",
|
||||
"Your password has been successfully changed!": "Votre mot de passe a été changé avec succès !",
|
||||
"Your response: %response%": "Votre réponse : %response%",
|
||||
"Your security questions can be used to recover an access to your account when you loose your password...": "Vos questions de sécurité peuvent être utilisées pour récupérer l'accès à votre compte lorsque vous perdez votre mot de passe...",
|
||||
"accepted his invitation to join the group": "a accepté son invitation à rejoindre le groupe",
|
||||
"accepted you request to join the group": "a accepté votre demande à rejoindre le groupe",
|
||||
"accepted your friendship request.": "a accepté cotre demande d'ami",
|
||||
"cancel": "annuler",
|
||||
"created a new post": "a créé un nouveau post",
|
||||
"delete": "supprimer",
|
||||
"https://www.youtube.com/watch/?v=": "https://www.youtube.com/watch/?v=",
|
||||
"invited you to join the group": "vous a invité à rejoindre le groupe",
|
||||
"on %user_name%'s page": "sur la page de %user_name%",
|
||||
"on his / her page": "sur sa page",
|
||||
"on the group %group%.": "sur le groupe %group%",
|
||||
"posted a comment": "a posté un commentaire",
|
||||
"rejected his invitation to join the group": "a rejeté son invitation à rejoindre le groupe",
|
||||
"rejected your friendship request.": "a rejeté votre demande d'ami",
|
||||
"rejected your request to join the group": "a rejeté votre demande à rejoindre le groupe",
|
||||
"sent a request to join the group": "a envoyé une demande à rejoindre le groupe",
|
||||
"sent you a friendship request.": "vous a envoyé une demande d'ami"
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import 'package:comunic/helpers/api_helper.dart';
|
||||
import 'package:comunic/helpers/preferences_helper.dart';
|
||||
import 'package:comunic/helpers/websocket_helper.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/authentication_details.dart';
|
||||
import 'package:comunic/models/login_tokens.dart';
|
||||
import 'package:comunic/models/new_account.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Account helper
|
||||
@ -16,6 +18,13 @@ enum AuthResult {
|
||||
INVALID_CREDENTIALS
|
||||
}
|
||||
|
||||
enum CreateAccountResult {
|
||||
SUCCESS,
|
||||
ERROR_TOO_MANY_REQUESTS,
|
||||
ERROR_EXISTING_EMAIL,
|
||||
ERROR
|
||||
}
|
||||
|
||||
class AccountHelper {
|
||||
static const _USER_ID_PREFERENCE_NAME = "user_id";
|
||||
|
||||
@ -74,8 +83,99 @@ class AccountHelper {
|
||||
Future<void> signOut() async {
|
||||
await (await PreferencesHelper.getInstance()).setLoginTokens(null);
|
||||
_currentUserID = 0;
|
||||
|
||||
// Close current web socket
|
||||
WebSocketHelper.close();
|
||||
}
|
||||
|
||||
/// Create a new user account
|
||||
Future<CreateAccountResult> createAccount(NewAccount info) async {
|
||||
final response = await APIRequest(
|
||||
uri: "account/create",
|
||||
needLogin: false,
|
||||
args: {
|
||||
"firstName": info.firstName,
|
||||
"lastName": info.lastName,
|
||||
"emailAddress": info.email,
|
||||
"password": info.password,
|
||||
},
|
||||
).exec();
|
||||
|
||||
switch (response.code) {
|
||||
case 200:
|
||||
return CreateAccountResult.SUCCESS;
|
||||
|
||||
case 409:
|
||||
return CreateAccountResult.ERROR_EXISTING_EMAIL;
|
||||
|
||||
case 429:
|
||||
return CreateAccountResult.ERROR_TOO_MANY_REQUESTS;
|
||||
|
||||
default:
|
||||
return CreateAccountResult.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check out whether a given email address exists or not
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<bool> existsMailAccount(String email) async =>
|
||||
(await APIRequest.withoutLogin("account/exists_email")
|
||||
.addString("email", email)
|
||||
.execWithThrow())
|
||||
.getObject()["exists"];
|
||||
|
||||
/// Check out whether security questions have been set for an account or not
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<bool> hasSecurityQuestions(String email) async =>
|
||||
(await APIRequest.withoutLogin("account/has_security_questions")
|
||||
.addString("email", email)
|
||||
.execWithThrow())
|
||||
.getObject()["defined"];
|
||||
|
||||
/// Get the security questions of the user
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<List<String>> getSecurityQuestions(String email) async =>
|
||||
((await APIRequest.withoutLogin("account/get_security_questions")
|
||||
.addString("email", email)
|
||||
.execWithThrow())
|
||||
.getObject()["questions"])
|
||||
.cast<String>();
|
||||
|
||||
/// Validate given security answers
|
||||
///
|
||||
/// Throws an [Exception] in case of failure
|
||||
///
|
||||
/// Returns a password reset token in case of success
|
||||
static Future<String> checkAnswers(
|
||||
String email, List<String> answers) async =>
|
||||
(await APIRequest.withoutLogin("account/check_security_answers")
|
||||
.addString("email", email)
|
||||
.addString("answers",
|
||||
answers.map((f) => Uri.encodeComponent(f)).join("&"))
|
||||
.execWithThrow())
|
||||
.getObject()["reset_token"];
|
||||
|
||||
/// Check a password reset token
|
||||
///
|
||||
/// Throws in case failure
|
||||
static Future<void> validatePasswordResetToken(String token) async =>
|
||||
await APIRequest.withoutLogin("account/check_password_reset_token")
|
||||
.addString("token", token)
|
||||
.execWithThrow();
|
||||
|
||||
/// Change account password using password reset token
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> changeAccountPassword(
|
||||
String token, String password) async =>
|
||||
await APIRequest.withoutLogin("account/reset_user_passwd")
|
||||
.addString("token", token)
|
||||
.addString("password", password)
|
||||
.execWithThrow();
|
||||
|
||||
/// Get current user ID from the server
|
||||
Future<int> _downloadCurrentUserID() async {
|
||||
final response = await APIRequest(
|
||||
@ -94,9 +194,29 @@ class AccountHelper {
|
||||
_currentUserID = preferences.getInt(_USER_ID_PREFERENCE_NAME);
|
||||
}
|
||||
|
||||
/// Check if current user ID is loaded or not
|
||||
static bool get isUserIDLoaded => _currentUserID > 0;
|
||||
|
||||
/// Get the ID of the currently signed in user
|
||||
static int getCurrentUserID() {
|
||||
if (_currentUserID == -1) throw "Current user ID has not been loaded yet!";
|
||||
return _currentUserID;
|
||||
}
|
||||
|
||||
/// Disconnect all the devices of the current user
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> disconnectAllDevices() async {
|
||||
await APIRequest(uri: "account/disconnect_all_devices", needLogin: true)
|
||||
.execWithThrow();
|
||||
}
|
||||
|
||||
/// Remove permanently a user account
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> deleteAccount(String password) async {
|
||||
await APIRequest(uri: "account/delete", needLogin: true)
|
||||
.addString("password", password)
|
||||
.execWithThrow();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:comunic/helpers/events_helper.dart';
|
||||
import 'package:comunic/helpers/preferences_helper.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/api_response.dart';
|
||||
@ -37,12 +38,42 @@ class APIHelper {
|
||||
else
|
||||
url = Uri.https(config().apiServerName, path);
|
||||
|
||||
final data = FormData.from(request.args);
|
||||
final data = FormData.fromMap(request.args);
|
||||
|
||||
// Process files (if required)
|
||||
if (multipart)
|
||||
request.files.forEach(
|
||||
(k, v) => data.add(k, UploadFileInfo(v, v.path.split("/").last)));
|
||||
if (multipart) {
|
||||
// Process filesystem files
|
||||
for (final key in request.files.keys) {
|
||||
var v = request.files[key];
|
||||
data.files.add(MapEntry(
|
||||
key,
|
||||
await MultipartFile.fromFile(v.path,
|
||||
filename: v.path.split("/").last)));
|
||||
}
|
||||
|
||||
// Process in-memory files
|
||||
for (final key in request.bytesFiles.keys) {
|
||||
var v = request.bytesFiles[key];
|
||||
data.files.add(MapEntry(
|
||||
key,
|
||||
MultipartFile.fromBytes(
|
||||
v.bytes,
|
||||
filename: v.filename.split("/").last,
|
||||
contentType: v.type,
|
||||
)));
|
||||
}
|
||||
|
||||
// Process picked files
|
||||
for (final key in request.pickedFiles.keys) {
|
||||
var v = request.pickedFiles[key];
|
||||
data.files.add(MapEntry(
|
||||
key,
|
||||
MultipartFile.fromBytes(
|
||||
await v.readAsBytes(),
|
||||
filename: v.path.split("/").last,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the request
|
||||
final response = await Dio().post(
|
||||
@ -55,13 +86,18 @@ class APIHelper {
|
||||
),
|
||||
);
|
||||
|
||||
// Check if login token is rejected by server
|
||||
if (response.statusCode == 412)
|
||||
EventsHelper.emit(InvalidLoginTokensEvent());
|
||||
|
||||
if (response.statusCode != HttpStatus.ok)
|
||||
return APIResponse(response.statusCode, null);
|
||||
return APIResponse(response.statusCode, response.data);
|
||||
|
||||
return APIResponse(response.statusCode, response.data);
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
print(e.toString());
|
||||
print("Could not execute a request!");
|
||||
print(stack);
|
||||
return APIResponse(-1, null);
|
||||
}
|
||||
}
|
||||
|
79
lib/helpers/calls_helper.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:comunic/helpers/websocket_helper.dart';
|
||||
import 'package:comunic/lists/call_members_list.dart';
|
||||
import 'package:comunic/models/call_config.dart';
|
||||
import 'package:comunic/models/call_member.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
|
||||
/// Calls helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class CallsHelper {
|
||||
/// Join a call
|
||||
static Future<void> join(int convID) async =>
|
||||
await ws("calls/join", {"convID": convID});
|
||||
|
||||
/// Leave a call
|
||||
static Future<void> leave(int convID) async =>
|
||||
await ws("calls/leave", {"convID": convID});
|
||||
|
||||
/// Get calls configuration
|
||||
static Future<CallConfig> getConfig() async {
|
||||
final response = await ws("calls/config", {});
|
||||
return CallConfig(
|
||||
iceServers: response["iceServers"].cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get current call members
|
||||
static Future<CallMembersList> getMembers(int callID) async =>
|
||||
CallMembersList()
|
||||
..addAll((await ws("calls/members", {"callID": callID}))
|
||||
.map((f) => CallMember(
|
||||
userID: f["userID"],
|
||||
status: f["ready"] ? MemberStatus.READY : MemberStatus.JOINED,
|
||||
))
|
||||
.toList()
|
||||
.cast<CallMember>());
|
||||
|
||||
/// Request an offer to access another peer's stream
|
||||
static Future<void> requestOffer(int callID, int peerID) async =>
|
||||
await ws("calls/request_offer", {"callID": callID, "peerID": peerID});
|
||||
|
||||
/// Send a Session Description message to the server
|
||||
static Future<void> sendSessionDescription(
|
||||
int callID, int peerID, RTCSessionDescription sdp) async =>
|
||||
await ws("calls/signal", {
|
||||
"callID": callID,
|
||||
"peerID": peerID,
|
||||
"type": "SDP",
|
||||
"data": jsonEncode(sdp.toMap())
|
||||
});
|
||||
|
||||
/// Send an IceCandidate
|
||||
static Future<void> sendIceCandidate(
|
||||
int callID, int peerID, RTCIceCandidate candidate) async =>
|
||||
await ws("calls/signal", {
|
||||
"callID": callID,
|
||||
"peerID": peerID,
|
||||
"type": "CANDIDATE",
|
||||
"data": jsonEncode(candidate.toMap())
|
||||
});
|
||||
|
||||
/// Mark ourselves as ready to stream to other peers
|
||||
static Future<void> markPeerReady(int callID) async =>
|
||||
await ws("calls/mark_ready", {"callID": callID});
|
||||
|
||||
/// Notify other peers that we stopped streaming
|
||||
///
|
||||
/// This method never throw
|
||||
static Future<void> notifyStoppedStreaming(int callID) async {
|
||||
try {
|
||||
await ws("calls/stop_streaming", {"callID": callID});
|
||||
} catch (e, stack) {
|
||||
print("$e\n$stack");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/comment.dart';
|
||||
import 'package:comunic/models/displayed_content.dart';
|
||||
import 'package:comunic/models/new_comment.dart';
|
||||
|
||||
/// Comments helper
|
||||
@ -16,7 +17,7 @@ class CommentsHelper {
|
||||
"content": comment.hasContent ? comment.content : "",
|
||||
});
|
||||
|
||||
if (comment.hasImage) request.addFile("image", comment.image);
|
||||
if (comment.hasImage) request.addPickedFile("image", comment.image);
|
||||
|
||||
final response = await request.execWithFiles();
|
||||
|
||||
@ -64,7 +65,7 @@ class CommentsHelper {
|
||||
userID: entry["userID"],
|
||||
postID: entry["postID"],
|
||||
timeSent: entry["time_sent"],
|
||||
content: entry["content"],
|
||||
content: DisplayedString(entry["content"]),
|
||||
imageURL: entry["img_url"],
|
||||
likes: entry["likes"],
|
||||
userLike: entry["userlike"],
|
||||
|
@ -1,14 +1,18 @@
|
||||
import 'package:comunic/helpers/database/conversation_messages_database_helper.dart';
|
||||
import 'package:comunic/helpers/database/conversations_database_helper.dart';
|
||||
import 'package:comunic/helpers/users_helper.dart';
|
||||
import 'package:comunic/helpers/websocket_helper.dart';
|
||||
import 'package:comunic/lists/conversation_messages_list.dart';
|
||||
import 'package:comunic/lists/conversations_list.dart';
|
||||
import 'package:comunic/lists/unread_conversations_list.dart';
|
||||
import 'package:comunic/lists/users_list.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/api_response.dart';
|
||||
import 'package:comunic/models/conversation.dart';
|
||||
import 'package:comunic/models/conversation_message.dart';
|
||||
import 'package:comunic/models/displayed_content.dart';
|
||||
import 'package:comunic/models/new_conversation_message.dart';
|
||||
import 'package:comunic/models/unread_conversation.dart';
|
||||
import 'package:comunic/utils/account_utils.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@ -19,6 +23,8 @@ import 'package:meta/meta.dart';
|
||||
enum SendMessageResult { SUCCESS, MESSAGE_REJECTED, FAILED }
|
||||
|
||||
class ConversationsHelper {
|
||||
static final _registeredConversations = Map<int, int>();
|
||||
|
||||
final ConversationsDatabaseHelper _conversationsDatabaseHelper =
|
||||
ConversationsDatabaseHelper();
|
||||
final ConversationMessagesDatabaseHelper _conversationMessagesDatabaseHelper =
|
||||
@ -32,8 +38,8 @@ class ConversationsHelper {
|
||||
await APIRequest(uri: "conversations/create", needLogin: true, args: {
|
||||
"name": settings.hasName ? settings.name : "false",
|
||||
"follow": settings.following ? "true" : "false",
|
||||
"users": settings.members.join(",")
|
||||
}).exec();
|
||||
"users": settings.members.join(","),
|
||||
}).addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers).exec();
|
||||
|
||||
if (response.code != 200) return -1;
|
||||
|
||||
@ -50,10 +56,13 @@ class ConversationsHelper {
|
||||
"following": settings.following ? "true" : "false"
|
||||
});
|
||||
|
||||
if (settings.isOwner || settings.canEveryoneAddMembers)
|
||||
request.addString("members", settings.members.join(","));
|
||||
|
||||
// Update all conversation settings, if possible
|
||||
if (settings.isOwner) {
|
||||
request.addString("name", settings.hasName ? settings.name : "false");
|
||||
request.addString("members", settings.members.join(","));
|
||||
request.addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers);
|
||||
}
|
||||
|
||||
final response = await request.exec();
|
||||
@ -89,7 +98,7 @@ class ConversationsHelper {
|
||||
|
||||
try {
|
||||
ConversationsList list = ConversationsList();
|
||||
response.getArray().forEach((f) => list.add(_apiToConversation(f)));
|
||||
response.getArray().forEach((f) => list.add(apiToConversation(f)));
|
||||
|
||||
// Update the database
|
||||
await _conversationsDatabaseHelper.clearTable();
|
||||
@ -119,7 +128,7 @@ class ConversationsHelper {
|
||||
|
||||
if (response.code != 200) return null;
|
||||
|
||||
final conversation = _apiToConversation(response.getObject());
|
||||
final conversation = apiToConversation(response.getObject());
|
||||
_conversationsDatabaseHelper.insertOrUpdate(conversation);
|
||||
return conversation;
|
||||
} on Exception catch (e) {
|
||||
@ -139,6 +148,19 @@ class ConversationsHelper {
|
||||
return _conversationsDatabaseHelper.get(id);
|
||||
}
|
||||
|
||||
/// Get information about a conversation. The method throws an [Exception] in
|
||||
/// case of failure
|
||||
///
|
||||
/// Return value of this method is never null.
|
||||
Future<Conversation> getSingleOrThrow(int id, {bool force = false}) async {
|
||||
final conv = await this.getSingle(id, force: force);
|
||||
|
||||
if (conv == null)
|
||||
throw Exception("Could not get information about the conversation!");
|
||||
|
||||
return conv;
|
||||
}
|
||||
|
||||
/// Get the name of a [conversation]. This requires information
|
||||
/// about the users of this conversation
|
||||
static String getConversationName(
|
||||
@ -183,7 +205,7 @@ class ConversationsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously get the name fo the conversation
|
||||
/// Asynchronously get the name of the conversation
|
||||
///
|
||||
/// Unlike the synchronous method, this method does not need information
|
||||
/// about the members of the conversation
|
||||
@ -202,16 +224,22 @@ class ConversationsHelper {
|
||||
}
|
||||
|
||||
/// Turn an API entry into a [Conversation] object
|
||||
Conversation _apiToConversation(Map<String, dynamic> map) {
|
||||
static Conversation apiToConversation(Map<String, dynamic> map) {
|
||||
return Conversation(
|
||||
id: map["ID"],
|
||||
ownerID: map["ID_owner"],
|
||||
lastActive: map["last_active"],
|
||||
name: map["name"] == false ? null : map["name"],
|
||||
following: map["following"] == 1,
|
||||
sawLastMessage: map["saw_last_message"] == 1,
|
||||
members: map["members"].map<int>((f) => int.parse(f)).toList(),
|
||||
);
|
||||
id: map["ID"],
|
||||
ownerID: map["ID_owner"],
|
||||
lastActive: map["last_active"],
|
||||
name: map["name"] == false ? null : map["name"],
|
||||
following: map["following"] == 1,
|
||||
sawLastMessage: map["saw_last_message"] == 1,
|
||||
members: List<int>.from(map["members"]),
|
||||
canEveryoneAddMembers: map["canEveryoneAddMembers"],
|
||||
callCapabilities: map["can_have_video_call"]
|
||||
? CallCapabilities.VIDEO
|
||||
: (map["can_have_call"]
|
||||
? CallCapabilities.AUDIO
|
||||
: CallCapabilities.NONE),
|
||||
isHavingCall: map["has_call_now"]);
|
||||
}
|
||||
|
||||
/// Parse a list of messages given by the server
|
||||
@ -223,10 +251,7 @@ class ConversationsHelper {
|
||||
ConversationMessagesList list = ConversationMessagesList();
|
||||
response.getArray().forEach((f) {
|
||||
list.add(
|
||||
_apiToConversationMessage(
|
||||
conversationID: conversationID,
|
||||
map: f,
|
||||
),
|
||||
apiToConversationMessage(f),
|
||||
);
|
||||
});
|
||||
|
||||
@ -310,8 +335,8 @@ class ConversationsHelper {
|
||||
},
|
||||
);
|
||||
|
||||
//Check for image
|
||||
if (message.hasImage) request.addFile("image", message.image);
|
||||
// Check for image
|
||||
if (message.hasImage) request.addPickedFile("image", message.image);
|
||||
|
||||
//Send the message
|
||||
APIResponse response;
|
||||
@ -327,6 +352,16 @@ class ConversationsHelper {
|
||||
return SendMessageResult.SUCCESS;
|
||||
}
|
||||
|
||||
/// Save / Update a message into the database
|
||||
Future<void> saveMessage(ConversationMessage msg) async {
|
||||
await _conversationMessagesDatabaseHelper.insertOrUpdate(msg);
|
||||
}
|
||||
|
||||
/// Remove a message from the database
|
||||
Future<void> removeMessage(int msgID) async {
|
||||
await _conversationMessagesDatabaseHelper.delete(msgID);
|
||||
}
|
||||
|
||||
/// Update a message content
|
||||
Future<bool> updateMessage(int id, String newContent) async {
|
||||
final response = await APIRequest(
|
||||
@ -336,9 +371,7 @@ class ConversationsHelper {
|
||||
|
||||
if (response.code != 200) return false;
|
||||
|
||||
// Update the message content locally
|
||||
return await _conversationMessagesDatabaseHelper.updateMessageContent(
|
||||
id: id, newContent: newContent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Delete permanently a message specified by its [id]
|
||||
@ -351,21 +384,60 @@ class ConversationsHelper {
|
||||
|
||||
if (response.code != 200) return false;
|
||||
|
||||
// Delete the message locally
|
||||
return await _conversationMessagesDatabaseHelper.delete(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get the list of unread conversations
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<UnreadConversationsList> getListUnread() async {
|
||||
final list = (await APIRequest.withLogin("conversations/get_list_unread")
|
||||
.execWithThrow())
|
||||
.getArray();
|
||||
|
||||
return UnreadConversationsList()
|
||||
..addAll(list.map((f) => UnreadConversation(
|
||||
id: f["id"],
|
||||
convName: f["conv_name"],
|
||||
lastActive: f["last_active"],
|
||||
userID: f["userID"],
|
||||
message: f["message"],
|
||||
)));
|
||||
}
|
||||
|
||||
/// Register a conversation : ask the server to notify about updates to the
|
||||
/// conversation through WebSocket
|
||||
Future<void> registerConversationEvents(int id) async {
|
||||
if (_registeredConversations.containsKey(id))
|
||||
_registeredConversations[id]++;
|
||||
else {
|
||||
_registeredConversations[id] = 1;
|
||||
await ws("\$main/register_conv", {"convID": id});
|
||||
}
|
||||
}
|
||||
|
||||
/// Un-register to conversation update events
|
||||
Future<void> unregisterConversationEvents(int id) async {
|
||||
if (!_registeredConversations.containsKey(id)) return;
|
||||
|
||||
_registeredConversations[id]--;
|
||||
|
||||
if (_registeredConversations[id] <= 0) {
|
||||
_registeredConversations.remove(id);
|
||||
await ws("\$main/unregister_conv", {"convID": id});
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn an API response into a ConversationMessage object
|
||||
ConversationMessage _apiToConversationMessage({
|
||||
@required int conversationID,
|
||||
@required Map<String, dynamic> map,
|
||||
}) {
|
||||
static ConversationMessage apiToConversationMessage(
|
||||
Map<String, dynamic> map,
|
||||
) {
|
||||
return ConversationMessage(
|
||||
id: map["ID"],
|
||||
conversationID: conversationID,
|
||||
conversationID: map["convID"],
|
||||
userID: map["ID_user"],
|
||||
timeInsert: map["time_insert"],
|
||||
message: map["message"],
|
||||
message: DisplayedString(map["message"]),
|
||||
imageURL: map["image_path"],
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import 'package:comunic/helpers/database/database_contract.dart';
|
||||
import 'package:comunic/helpers/database/model_database_helper.dart';
|
||||
import 'package:comunic/lists/conversation_messages_list.dart';
|
||||
import 'package:comunic/models/conversation_message.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Conversation messages database helper
|
||||
///
|
||||
@ -35,26 +34,4 @@ class ConversationMessagesDatabaseHelper
|
||||
finalList.addAll(list);
|
||||
return finalList;
|
||||
}
|
||||
|
||||
/// Update the content of a message
|
||||
Future<bool> updateMessageContent({
|
||||
@required int id,
|
||||
@required String newContent,
|
||||
}) async {
|
||||
assert(id != null);
|
||||
assert(newContent != null);
|
||||
|
||||
final message = await get(id);
|
||||
|
||||
if(message == null)
|
||||
return false;
|
||||
|
||||
// Update the conversation message using the map
|
||||
final map = message.toMap();
|
||||
map[ConversationsMessagesTableContract.C_MESSAGE] = newContent;
|
||||
|
||||
await insertOrUpdate(ConversationMessage.fromMap(map));
|
||||
|
||||
return true; // Success
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
/// Main information
|
||||
class DatabaseContract {
|
||||
static const DATABASE_VERSION = 1;
|
||||
static const DATABASE_VERSION = 2;
|
||||
static const DATABASE_FILE_NAME = "database.sqlite";
|
||||
}
|
||||
|
||||
@ -19,9 +19,10 @@ abstract class UserTableContract {
|
||||
static const C_ID = BaseTableContract.C_ID;
|
||||
static const C_FIRST_NAME = "first_name";
|
||||
static const C_LAST_NAME = "last_name";
|
||||
static const C_VISIBILITY = "visibility";
|
||||
static const C_VISIBILITY = "visibility";
|
||||
static const C_VIRTUAL_DIRECTORY = "virtual_directory";
|
||||
static const C_ACCOUNT_IMAGE_URL = "account_image_url";
|
||||
static const C_CUSTOM_EMOJIES = "custom_emojies";
|
||||
}
|
||||
|
||||
/// Conversations table contract
|
||||
@ -34,6 +35,7 @@ abstract class ConversationTableContract {
|
||||
static const C_FOLLOWING = "following";
|
||||
static const C_SAW_LAST_MESSAGE = "saw_last_message";
|
||||
static const C_MEMBERS = "members";
|
||||
static const C_CAN_EVERYONE_ADD_MEMBERS = "can_everyone_add_members";
|
||||
}
|
||||
|
||||
/// Conversations messages table contract
|
||||
@ -55,4 +57,4 @@ abstract class FriendsListTableContract {
|
||||
static const C_LAST_ACTIVE = "last_active";
|
||||
static const C_FOLLOWING = "following";
|
||||
static const C_CAN_POST_TEXTS = "can_post_texts";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:comunic/helpers/database/database_contract.dart';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
@ -29,6 +30,13 @@ abstract class DatabaseHelper {
|
||||
return _db;
|
||||
}
|
||||
|
||||
/// Cleanup database
|
||||
static Future<void> cleanUpDatabase() async {
|
||||
// If connected to a network, cleanup user information
|
||||
if (await Connectivity().checkConnectivity() != ConnectivityResult.none)
|
||||
await _db.execute("DELETE FROM ${UserTableContract.TABLE_NAME}");
|
||||
}
|
||||
|
||||
/// Perform database update
|
||||
///
|
||||
/// Currently : delete all the database tables and initialize it again
|
||||
@ -46,8 +54,8 @@ abstract class DatabaseHelper {
|
||||
"DROP TABLE IF EXISTS ${ConversationsMessagesTableContract.TABLE_NAME}");
|
||||
|
||||
// Drop friends list table
|
||||
await db.execute(
|
||||
"DROP TABLE IF EXISTS ${FriendsListTableContract.TABLE_NAME}");
|
||||
await db
|
||||
.execute("DROP TABLE IF EXISTS ${FriendsListTableContract.TABLE_NAME}");
|
||||
|
||||
// Initialize database again
|
||||
await _initializeDatabase(db, newVersion);
|
||||
@ -62,7 +70,8 @@ abstract class DatabaseHelper {
|
||||
"${UserTableContract.C_LAST_NAME} TEXT, "
|
||||
"${UserTableContract.C_VISIBILITY} TEXT, "
|
||||
"${UserTableContract.C_VIRTUAL_DIRECTORY} TEXT, "
|
||||
"${UserTableContract.C_ACCOUNT_IMAGE_URL} TEXT"
|
||||
"${UserTableContract.C_ACCOUNT_IMAGE_URL} TEXT, "
|
||||
"${UserTableContract.C_CUSTOM_EMOJIES} TEXT"
|
||||
")");
|
||||
|
||||
// Create conversations table
|
||||
@ -73,7 +82,8 @@ abstract class DatabaseHelper {
|
||||
"${ConversationTableContract.C_NAME} TEXT, "
|
||||
"${ConversationTableContract.C_FOLLOWING} INTEGER, "
|
||||
"${ConversationTableContract.C_SAW_LAST_MESSAGE} INTEGER, "
|
||||
"${ConversationTableContract.C_MEMBERS} TEXT"
|
||||
"${ConversationTableContract.C_MEMBERS} TEXT, "
|
||||
"${ConversationTableContract.C_CAN_EVERYONE_ADD_MEMBERS} INTEGER"
|
||||
")");
|
||||
|
||||
// Create conversation messages table
|
||||
|
150
lib/helpers/events_helper.dart
Normal file
@ -0,0 +1,150 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:comunic/models/comment.dart';
|
||||
import 'package:comunic/models/conversation_message.dart';
|
||||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
|
||||
/// Events helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
/// Invalid login token
|
||||
class InvalidLoginTokensEvent {}
|
||||
|
||||
/// Main WebSocket closed
|
||||
class WSClosedEvent {}
|
||||
|
||||
/// New number of notifications
|
||||
class NewNumberNotifsEvent {
|
||||
final int newNum;
|
||||
|
||||
NewNumberNotifsEvent(this.newNum);
|
||||
}
|
||||
|
||||
/// New number of unread conversations
|
||||
class NewNumberUnreadConversations {
|
||||
final int newNum;
|
||||
|
||||
NewNumberUnreadConversations(this.newNum);
|
||||
}
|
||||
|
||||
/// New comment
|
||||
class NewCommentEvent {
|
||||
final Comment comment;
|
||||
|
||||
NewCommentEvent(this.comment);
|
||||
}
|
||||
|
||||
/// Updated comment
|
||||
class UpdatedCommentEvent {
|
||||
final Comment comment;
|
||||
|
||||
UpdatedCommentEvent(this.comment);
|
||||
}
|
||||
|
||||
/// Deleted comment
|
||||
class DeletedCommentEvent {
|
||||
final int commentID;
|
||||
|
||||
DeletedCommentEvent(this.commentID);
|
||||
}
|
||||
|
||||
/// New conversation message
|
||||
class NewConversationMessageEvent {
|
||||
final ConversationMessage msg;
|
||||
|
||||
NewConversationMessageEvent(this.msg);
|
||||
}
|
||||
|
||||
/// Updated conversation message
|
||||
class UpdatedConversationMessageEvent {
|
||||
final ConversationMessage msg;
|
||||
|
||||
UpdatedConversationMessageEvent(this.msg);
|
||||
}
|
||||
|
||||
/// Deleted conversation message
|
||||
class DeletedConversationMessageEvent {
|
||||
final ConversationMessage msg;
|
||||
|
||||
DeletedConversationMessageEvent(this.msg);
|
||||
}
|
||||
|
||||
/// User joined call event
|
||||
class UserJoinedCallEvent {
|
||||
final int callID;
|
||||
final int userID;
|
||||
|
||||
UserJoinedCallEvent(this.callID, this.userID);
|
||||
}
|
||||
|
||||
/// User left call event
|
||||
class UserLeftCallEvent {
|
||||
final int callID;
|
||||
final int userID;
|
||||
|
||||
UserLeftCallEvent(this.callID, this.userID);
|
||||
}
|
||||
|
||||
/// New call signal event
|
||||
class NewCallSignalEvent {
|
||||
final int callID;
|
||||
final int peerID;
|
||||
final RTCSessionDescription sessionDescription;
|
||||
final RTCIceCandidate candidate;
|
||||
|
||||
const NewCallSignalEvent({
|
||||
this.callID,
|
||||
this.peerID,
|
||||
this.sessionDescription,
|
||||
this.candidate,
|
||||
}) : assert(callID != null),
|
||||
assert(peerID != null),
|
||||
assert(sessionDescription != null || candidate != null);
|
||||
}
|
||||
|
||||
/// Call peer ready event
|
||||
class CallPeerReadyEvent {
|
||||
final int callID;
|
||||
final int peerID;
|
||||
|
||||
CallPeerReadyEvent(this.callID, this.peerID);
|
||||
}
|
||||
|
||||
/// Call peer interrupted streaming event
|
||||
class CallPeerInterruptedStreamingEvent {
|
||||
final int callID;
|
||||
final int peerID;
|
||||
|
||||
CallPeerInterruptedStreamingEvent(this.callID, this.peerID);
|
||||
}
|
||||
|
||||
/// Call closed event
|
||||
class CallClosedEvent {
|
||||
final int callID;
|
||||
|
||||
CallClosedEvent(this.callID);
|
||||
}
|
||||
|
||||
class EventsHelper {
|
||||
static EventBus _mgr = EventBus();
|
||||
|
||||
/// Listen to event
|
||||
///
|
||||
/// Do not use this method directly. You should instead prefer to use
|
||||
/// [SafeState.listen] to handle safely widgets lifecycle...
|
||||
///
|
||||
/// You can not register to global events
|
||||
static StreamSubscription<T> on<T>(void onData(T event)) {
|
||||
if (T == dynamic) throw Exception("Do not register to all events!");
|
||||
|
||||
final stream = _mgr.on<T>();
|
||||
return stream.listen(onData);
|
||||
}
|
||||
|
||||
/// Propagate an event
|
||||
static void emit<T>(T event) {
|
||||
_mgr.fire(event);
|
||||
}
|
||||
}
|
@ -28,18 +28,12 @@ class FriendsHelper {
|
||||
if (response.code != 200) return null;
|
||||
|
||||
// Parse and return the list of friends
|
||||
FriendsList list = FriendsList();
|
||||
response.getArray().forEach(
|
||||
(f) => list.add(
|
||||
Friend(
|
||||
id: f["ID_friend"],
|
||||
accepted: f["accepted"] == 1,
|
||||
lastActive: f["time_last_activity"],
|
||||
following: f["following"] == 1,
|
||||
canPostTexts: f["canPostTexts"],
|
||||
),
|
||||
),
|
||||
);
|
||||
FriendsList list = FriendsList()
|
||||
..addAll(response
|
||||
.getArray()
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(apiToFriend)
|
||||
.toList());
|
||||
|
||||
// Save the list of friends
|
||||
_friendsDatabaseHelper.clearTable();
|
||||
@ -48,6 +42,17 @@ class FriendsHelper {
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Turn an API entry into a [Friend] object
|
||||
static Friend apiToFriend(Map<String, dynamic> row) {
|
||||
return Friend(
|
||||
id: row["ID_friend"],
|
||||
accepted: row["accepted"] == 1,
|
||||
lastActive: row["time_last_activity"],
|
||||
following: row["following"] == 1,
|
||||
canPostTexts: row["canPostTexts"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the list, either from an online or an offline source
|
||||
Future<FriendsList> getList({@required bool online}) async {
|
||||
if (online)
|
||||
|
@ -1,6 +1,13 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:comunic/lists/group_members_list.dart';
|
||||
import 'package:comunic/lists/groups_list.dart';
|
||||
import 'package:comunic/models/advanced_group_info.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/group.dart';
|
||||
import 'package:comunic/models/group_membership.dart';
|
||||
import 'package:comunic/utils/api_utils.dart';
|
||||
import 'package:comunic/utils/map_utils.dart';
|
||||
|
||||
/// Groups helper
|
||||
///
|
||||
@ -34,6 +41,17 @@ const _APIGroupsPostsCreationLevelsMap = {
|
||||
|
||||
final _groupsListCache = GroupsList();
|
||||
|
||||
/// Callback for getting advanced user information
|
||||
enum GetAdvancedInfoStatus { SUCCESS, ACCESS_DENIED }
|
||||
|
||||
class GetAdvancedInfoResult {
|
||||
final GetAdvancedInfoStatus status;
|
||||
final AdvancedGroupInfo info;
|
||||
|
||||
GetAdvancedInfoResult(this.status, this.info) : assert(status != null);
|
||||
}
|
||||
|
||||
/// Groups helper
|
||||
class GroupsHelper {
|
||||
/// Download a list of groups information from the server
|
||||
Future<GroupsList> _downloadList(Set<int> groups) async {
|
||||
@ -72,7 +90,7 @@ class GroupsHelper {
|
||||
// Check which groups information to download
|
||||
final toDownload = Set<int>();
|
||||
groups.forEach((groupID) {
|
||||
if (_groupsListCache.containsKey(groupID))
|
||||
if (!force && _groupsListCache.containsKey(groupID))
|
||||
list[groupID] = _groupsListCache[groupID];
|
||||
else
|
||||
toDownload.add(groupID);
|
||||
@ -91,18 +109,268 @@ class GroupsHelper {
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Get information about a single group
|
||||
///
|
||||
/// Throws in case of failure
|
||||
Future<Group> getSingle(int groupID, {bool force = false}) async {
|
||||
return (await getListOrThrow(Set<int>()..add(groupID), force: force))
|
||||
.values
|
||||
.first;
|
||||
}
|
||||
|
||||
/// Get the list of groups of a user
|
||||
Future<Set<int>> getListUser() async =>
|
||||
(await APIRequest(uri: "groups/get_my_list", needLogin: true).exec())
|
||||
.assertOk()
|
||||
.getArray()
|
||||
.map((f) => cast<int>(f))
|
||||
.toSet();
|
||||
|
||||
/// Create a new group
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<int> create(String name) async {
|
||||
final result = await APIRequest.withLogin("groups/create")
|
||||
.addString("name", name)
|
||||
.execWithThrow();
|
||||
|
||||
return result.getObject()["id"];
|
||||
}
|
||||
|
||||
/// Perform a simple membership request
|
||||
Future<bool> _simpleMembershipRequest(int groupID, String uri,
|
||||
{Map<String, String> args}) async =>
|
||||
(await (APIRequest(uri: uri, needLogin: true)
|
||||
..addInt("id", groupID)
|
||||
..addArgs(args == null ? Map() : args))
|
||||
.exec())
|
||||
.isOK;
|
||||
|
||||
/// Remove group membership
|
||||
Future<bool> removeMembership(int groupID) async =>
|
||||
_simpleMembershipRequest(groupID, "groups/remove_membership");
|
||||
|
||||
/// Cancel membership request
|
||||
Future<bool> cancelRequest(int groupID) async =>
|
||||
_simpleMembershipRequest(groupID, "groups/cancel_request");
|
||||
|
||||
/// Send a new membership request
|
||||
Future<bool> sendRequest(int groupID) async =>
|
||||
_simpleMembershipRequest(groupID, "groups/send_request");
|
||||
|
||||
/// Respond to a group membership invitation
|
||||
Future<bool> respondInvitation(int groupID, bool accept) async =>
|
||||
_simpleMembershipRequest(groupID, "groups/respond_invitation", args: {
|
||||
"accept": accept ? "true" : "false",
|
||||
});
|
||||
|
||||
/// Update group following status
|
||||
Future<bool> setFollowing(int groupID, bool follow) async =>
|
||||
(await (APIRequest(uri: "groups/set_following", needLogin: true)
|
||||
..addInt("groupID", groupID)
|
||||
..addBool("follow", follow))
|
||||
.exec())
|
||||
.isOK;
|
||||
|
||||
/// Get advanced information about the user
|
||||
Future<GetAdvancedInfoResult> getAdvancedInfo(int groupID) async {
|
||||
// Get advanced information about the user
|
||||
final result =
|
||||
await (APIRequest(uri: "groups/get_advanced_info", needLogin: true)
|
||||
..addInt("id", groupID))
|
||||
.exec();
|
||||
|
||||
switch (result.code) {
|
||||
case 401:
|
||||
return GetAdvancedInfoResult(GetAdvancedInfoStatus.ACCESS_DENIED, null);
|
||||
|
||||
case 200:
|
||||
return GetAdvancedInfoResult(GetAdvancedInfoStatus.SUCCESS,
|
||||
_getAdvancedGroupInfoFromAPI(result.getObject()));
|
||||
|
||||
default:
|
||||
throw Exception("Could not get advanced group information!");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get group settings
|
||||
///
|
||||
/// This function is currently a kind of alias, but it might
|
||||
/// change in the future
|
||||
///
|
||||
/// Throws in case of error
|
||||
Future<AdvancedGroupInfo> getSettings(int groupID) async {
|
||||
final groupInfo = await getAdvancedInfo(groupID);
|
||||
|
||||
if (groupInfo.status != GetAdvancedInfoStatus.SUCCESS)
|
||||
throw Exception("Could not get group information!");
|
||||
|
||||
return groupInfo.info;
|
||||
}
|
||||
|
||||
/// Check the availability of a virtual directory
|
||||
///
|
||||
/// Throws in case of error
|
||||
static Future<void> checkVirtualDirectoryAvailability(
|
||||
int groupID, String dir) async =>
|
||||
await APIRequest(uri: "groups/checkVirtualDirectory", needLogin: true)
|
||||
.addInt("groupID", groupID)
|
||||
.addString("directory", dir)
|
||||
.execWithThrow();
|
||||
|
||||
/// Update (set) new group settings
|
||||
///
|
||||
/// Throws in case of error
|
||||
static Future<void> setSettings(AdvancedGroupInfo settings) async {
|
||||
await APIRequest(uri: "groups/set_settings", needLogin: true)
|
||||
.addInt("id", settings.id)
|
||||
.addString("name", settings.name)
|
||||
.addString("virtual_directory", settings.virtualDirectory)
|
||||
.addString("visibility",
|
||||
invertMap(_APIGroupsVisibilityLevelsMap)[settings.visibilityLevel])
|
||||
.addString(
|
||||
"registration_level",
|
||||
invertMap(
|
||||
_APIGroupsRegistrationLevelsMap)[settings.registrationLevel])
|
||||
.addString(
|
||||
"posts_level",
|
||||
invertMap(
|
||||
_APIGroupsPostsCreationLevelsMap)[settings.postCreationLevel])
|
||||
.addString("description", settings.description)
|
||||
.addString("url", settings.url)
|
||||
.execWithThrow();
|
||||
}
|
||||
|
||||
/// Upload a new logo
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> uploadNewLogo(int groupID, Uint8List bytes) async =>
|
||||
await APIRequest(uri: "groups/upload_logo", needLogin: true)
|
||||
.addInt("id", groupID)
|
||||
.addBytesFile("logo", BytesFile("logo.png", bytes))
|
||||
.execWithFilesAndThrow();
|
||||
|
||||
/// Delete group logo
|
||||
///
|
||||
/// Throws in case of error
|
||||
static Future<void> deleteLogo(int groupID) async =>
|
||||
await APIRequest(uri: "groups/delete_logo", needLogin: true)
|
||||
.addInt("id", groupID)
|
||||
.execWithThrow();
|
||||
|
||||
/// Delete a group
|
||||
///
|
||||
/// Throws in case of error
|
||||
static Future<void> deleteGroup(int groupID, String password) async =>
|
||||
await APIRequest(uri: "groups/delete", needLogin: true)
|
||||
.addInt("groupID", groupID)
|
||||
.addString("password", password)
|
||||
.execWithThrow();
|
||||
|
||||
/// Get the list of members of the group
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<GroupMembersList> getMembersList(int groupID) async =>
|
||||
GroupMembersList()
|
||||
..addAll((await APIRequest(uri: "groups/get_members", needLogin: true)
|
||||
.addInt("id", groupID)
|
||||
.execWithThrow())
|
||||
.getArray()
|
||||
.map((f) => _apiToGroupMembership(f))
|
||||
.toList());
|
||||
|
||||
/// Invite a user to join a group
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> sendInvitation(int groupID, int userID) async =>
|
||||
APIRequest.withLogin("groups/invite")
|
||||
.addInt("group_id", groupID)
|
||||
.addInt("userID", userID)
|
||||
.execWithThrow();
|
||||
|
||||
/// Cancel a group membership invitation
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> cancelInvitation(int groupID, int userID) async =>
|
||||
await APIRequest.withLogin("groups/cancel_invitation")
|
||||
.addInt("groupID", groupID)
|
||||
.addInt("userID", userID)
|
||||
.execWithThrow();
|
||||
|
||||
/// Respond to a group membership request
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> respondRequest(
|
||||
int groupID, int userID, bool accept) async =>
|
||||
await APIRequest.withLogin("groups/respond_request")
|
||||
.addInt("groupID", groupID)
|
||||
.addInt("userID", userID)
|
||||
.addBool("accept", accept)
|
||||
.execWithThrow();
|
||||
|
||||
/// Remove a member from a group
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> removeMemberFromGroup(int groupID, int userID) async =>
|
||||
APIRequest.withLogin("groups/delete_member")
|
||||
.addInt("groupID", groupID)
|
||||
.addInt("userID", userID)
|
||||
.execWithThrow();
|
||||
|
||||
/// Change the membership level of a member of a group
|
||||
///
|
||||
/// Throws an exception in case of failure
|
||||
static Future<void> setNewLevel(
|
||||
int groupID, int userID, GroupMembershipLevel level) async =>
|
||||
await APIRequest.withLogin("groups/update_membership_level")
|
||||
.addInt("groupID", groupID)
|
||||
.addInt("userID", userID)
|
||||
.addString("level", invertMap(_APIGroupsMembershipLevelsMap)[level])
|
||||
.execWithThrow();
|
||||
|
||||
/// Turn an API entry into a group object
|
||||
Group _getGroupFromAPI(Map<String, dynamic> map) {
|
||||
return Group(
|
||||
id: map["id"],
|
||||
name: map["name"],
|
||||
iconURL: map["icon_url"],
|
||||
numberMembers: map["number_members"],
|
||||
membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]],
|
||||
visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]],
|
||||
registrationLevel:
|
||||
_APIGroupsRegistrationLevelsMap[map["registration_level"]],
|
||||
postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]],
|
||||
virtualDirectory: map["virtual_directory"],
|
||||
virtualDirectory: nullToEmpty(map["virtual_directory"]),
|
||||
following: map["following"]);
|
||||
}
|
||||
|
||||
/// Get advanced group information
|
||||
AdvancedGroupInfo _getAdvancedGroupInfoFromAPI(Map<String, dynamic> map) =>
|
||||
AdvancedGroupInfo(
|
||||
id: map["id"],
|
||||
name: map["name"],
|
||||
iconURL: map["icon_url"],
|
||||
numberMembers: map["number_members"],
|
||||
membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]],
|
||||
visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]],
|
||||
registrationLevel:
|
||||
_APIGroupsRegistrationLevelsMap[map["registration_level"]],
|
||||
postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]],
|
||||
virtualDirectory: nullToEmpty(map["virtual_directory"]),
|
||||
following: map["following"],
|
||||
timeCreate: map["time_create"],
|
||||
description: nullToEmpty(map["description"]),
|
||||
url: nullToEmpty(map["url"]),
|
||||
likes: map["number_likes"],
|
||||
userLike: map["is_liking"],
|
||||
);
|
||||
|
||||
/// Create [GroupMembership] object from API entry
|
||||
static GroupMembership _apiToGroupMembership(Map<String, dynamic> row) =>
|
||||
GroupMembership(
|
||||
userID: row["user_id"],
|
||||
groupID: row["group_id"],
|
||||
timeCreate: row["time_create"],
|
||||
level: _APIGroupsMembershipLevelsMap[row["level"]],
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:comunic/enums/likes_type.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/helpers/websocket_helper.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Likes helper
|
||||
@ -15,21 +15,15 @@ const LikesAPIMap = {
|
||||
|
||||
class LikesHelper {
|
||||
/// Update liking status of an element
|
||||
Future<bool> setLiking({
|
||||
Future<void> setLiking({
|
||||
@required LikesType type,
|
||||
@required bool like,
|
||||
@required int id,
|
||||
}) async {
|
||||
return (await APIRequest(
|
||||
uri: "likes/update",
|
||||
needLogin: true,
|
||||
args: {
|
||||
"type": LikesAPIMap[type],
|
||||
"like": like.toString(),
|
||||
"id": id.toString(),
|
||||
},
|
||||
).exec())
|
||||
.code ==
|
||||
200;
|
||||
return (await ws("likes/update", {
|
||||
"type": LikesAPIMap[type],
|
||||
"like": like.toString(),
|
||||
"id": id.toString(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:comunic/lists/notifications_list.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/count_unread_notifications.dart';
|
||||
import 'package:comunic/models/notification.dart';
|
||||
|
||||
/// Notifications helper
|
||||
@ -47,6 +48,22 @@ const _NotificationsTypeAPImapping = {
|
||||
};
|
||||
|
||||
class NotificationsHelper {
|
||||
/// Get the number of unread notifications
|
||||
///
|
||||
/// This method throws in case of error
|
||||
Future<CountUnreadNotifications> countUnread() async {
|
||||
final response =
|
||||
await APIRequest(uri: "notifications/count_all_news", needLogin: true)
|
||||
.exec();
|
||||
|
||||
final content = response.assertOk().getObject();
|
||||
|
||||
return CountUnreadNotifications(
|
||||
notifications: content["notifications"],
|
||||
conversations: content["conversations"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the list of unread notifications of the user
|
||||
Future<NotificationsList> getUnread() async {
|
||||
final response =
|
||||
@ -85,4 +102,10 @@ class NotificationsHelper {
|
||||
},
|
||||
).exec())
|
||||
.isOK;
|
||||
|
||||
/// Delete all unread notifications
|
||||
Future<bool> deleteAllNotifications() async =>
|
||||
(await APIRequest(uri: "notifications/delete_all", needLogin: true)
|
||||
.exec())
|
||||
.isOK;
|
||||
}
|
||||
|
@ -4,11 +4,14 @@ import 'package:comunic/enums/post_visibility_level.dart';
|
||||
import 'package:comunic/enums/user_access_levels.dart';
|
||||
import 'package:comunic/helpers/comments_helper.dart';
|
||||
import 'package:comunic/helpers/survey_helper.dart';
|
||||
import 'package:comunic/helpers/websocket_helper.dart';
|
||||
import 'package:comunic/lists/comments_list.dart';
|
||||
import 'package:comunic/lists/posts_list.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/displayed_content.dart';
|
||||
import 'package:comunic/models/new_post.dart';
|
||||
import 'package:comunic/models/post.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
/// Posts helper
|
||||
///
|
||||
@ -45,6 +48,12 @@ const _APIPostsTargetKindsMap = {
|
||||
};
|
||||
|
||||
class PostsHelper {
|
||||
/// Stores the list of posts we are registered to
|
||||
///
|
||||
/// First int = post ID
|
||||
/// Second int = number of registered people
|
||||
static final _registeredPosts = Map<int, int>();
|
||||
|
||||
/// Get the list of latest posts. Return the list of posts or null in case of
|
||||
/// failure
|
||||
Future<PostsList> getLatest({int from = 0}) async {
|
||||
@ -67,10 +76,27 @@ class PostsHelper {
|
||||
|
||||
/// Get the list of posts of a user
|
||||
Future<PostsList> getUserPosts(int userID, {int from = 0}) async {
|
||||
final response = await APIRequest(
|
||||
uri: "posts/get_user",
|
||||
needLogin: true,
|
||||
args: {"userID": userID.toString(), "startFrom": from.toString()})
|
||||
final response = await (APIRequest(uri: "posts/get_user", needLogin: true)
|
||||
..addInt("userID", userID)
|
||||
..addInt("startFrom", from == 0 ? 0 : from - 1))
|
||||
.exec();
|
||||
|
||||
if (response.code != 200) return null;
|
||||
|
||||
try {
|
||||
// Parse & return the list of posts
|
||||
return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the list of posts of a group
|
||||
Future<PostsList> getGroupPosts(int groupID, {int from = 0}) async {
|
||||
final response = await (APIRequest(uri: "posts/get_group", needLogin: true)
|
||||
..addInt("groupID", groupID)
|
||||
..addInt("startFrom", from == 0 ? 0 : from - 1))
|
||||
.exec();
|
||||
|
||||
if (response.code != 200) return null;
|
||||
@ -117,7 +143,33 @@ class PostsHelper {
|
||||
break;
|
||||
|
||||
case PostKind.IMAGE:
|
||||
request.addFile("image", post.image);
|
||||
request.addPickedFile("image", post.image);
|
||||
break;
|
||||
|
||||
case PostKind.WEB_LINK:
|
||||
request.addString("url", post.url);
|
||||
break;
|
||||
|
||||
case PostKind.PDF:
|
||||
request.addBytesFile(
|
||||
"pdf",
|
||||
BytesFile("file.pdf", post.pdf,
|
||||
type: MediaType.parse("application/pdf")));
|
||||
break;
|
||||
|
||||
case PostKind.COUNTDOWN:
|
||||
request.addInt(
|
||||
"time-end", (post.timeEnd.millisecondsSinceEpoch / 1000).floor());
|
||||
break;
|
||||
|
||||
case PostKind.SURVEY:
|
||||
request.addString("question", post.survey.question);
|
||||
request.addString("answers", post.survey.answers.join("<>"));
|
||||
request.addBool("allowNewAnswers", post.survey.allowNewChoicesCreation);
|
||||
break;
|
||||
|
||||
case PostKind.YOUTUBE:
|
||||
request.addString("youtube_id", post.youtubeId);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -167,6 +219,28 @@ class PostsHelper {
|
||||
.isOK;
|
||||
}
|
||||
|
||||
/// Register to a post events
|
||||
Future<void> registerPostEvents(int id) async {
|
||||
if (_registeredPosts.containsKey(id))
|
||||
_registeredPosts[id]++;
|
||||
else {
|
||||
_registeredPosts[id] = 1;
|
||||
await ws("\$main/register_post", {"postID": id});
|
||||
}
|
||||
}
|
||||
|
||||
/// Un-register to post events
|
||||
Future<void> unregisterPostEvents(int id) async {
|
||||
if (!_registeredPosts.containsKey(id)) return;
|
||||
|
||||
_registeredPosts[id]--;
|
||||
|
||||
if (_registeredPosts[id] <= 0) {
|
||||
_registeredPosts.remove(id);
|
||||
await ws("\$main/unregister_post", {"postID": id});
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn an API entry into a [Post] object
|
||||
Post _apiToPost(Map<String, dynamic> map) {
|
||||
final postKind = _APIPostsKindsMap[map["kind"]];
|
||||
@ -189,7 +263,7 @@ class PostsHelper {
|
||||
userPageID: map["user_page_id"],
|
||||
groupID: map["group_id"],
|
||||
timeSent: map["post_time"],
|
||||
content: map["content"],
|
||||
content: DisplayedString(map["content"]),
|
||||
visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]],
|
||||
kind: postKind,
|
||||
fileSize: map["file_size"],
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:comunic/models/application_preferences.dart';
|
||||
import 'package:comunic/models/login_tokens.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@ -9,11 +10,18 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
enum PreferencesKeyList { LOGIN_TOKENS, ENABLE_DARK_THEME }
|
||||
enum PreferencesKeyList {
|
||||
LOGIN_TOKENS,
|
||||
ENABLE_DARK_THEME,
|
||||
FORCE_MOBILE_MODE,
|
||||
SHOW_PERFORMANCE_OVERLAY,
|
||||
}
|
||||
|
||||
const _PreferenceKeysName = {
|
||||
PreferencesKeyList.LOGIN_TOKENS: "login_tokens",
|
||||
PreferencesKeyList.ENABLE_DARK_THEME: "dark_theme",
|
||||
PreferencesKeyList.FORCE_MOBILE_MODE: "force_mobile_mode",
|
||||
PreferencesKeyList.SHOW_PERFORMANCE_OVERLAY: "perfs_overlay",
|
||||
};
|
||||
|
||||
class PreferencesHelper {
|
||||
@ -70,6 +78,19 @@ class PreferencesHelper {
|
||||
final v = _sharedPreferences.getBool(_PreferenceKeysName[key]);
|
||||
return v == null ? alternative : v;
|
||||
}
|
||||
|
||||
/// Get all settings as an [ApplicationPreferences] object
|
||||
ApplicationPreferences get preferences => ApplicationPreferences(
|
||||
enableDarkMode: getBool(PreferencesKeyList.ENABLE_DARK_THEME),
|
||||
forceMobileMode: getBool(PreferencesKeyList.FORCE_MOBILE_MODE),
|
||||
showPerformancesOverlay:
|
||||
getBool(PreferencesKeyList.SHOW_PERFORMANCE_OVERLAY));
|
||||
|
||||
/// Apply new preferences
|
||||
Future<void> setPreferences(ApplicationPreferences preferences) async {
|
||||
await setBool(
|
||||
PreferencesKeyList.ENABLE_DARK_THEME, preferences.enableDarkMode);
|
||||
}
|
||||
}
|
||||
|
||||
PreferencesHelper preferences() {
|
||||
|
@ -1,31 +1,47 @@
|
||||
import 'package:comunic/helpers/users_helper.dart';
|
||||
import 'package:comunic/lists/search_results_list.dart';
|
||||
import 'package:comunic/lists/users_list.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/utils/list_utils.dart';
|
||||
import 'package:comunic/models/search_result.dart';
|
||||
import 'package:comunic/utils/api_utils.dart';
|
||||
|
||||
/// Search helper
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class SearchHelper {
|
||||
|
||||
/// Search for user. This method returns information about the target users
|
||||
///
|
||||
/// Returns information about the target users or null if an error occurred
|
||||
Future<UsersList> searchUser(String query) async {
|
||||
// Execute the query on the server
|
||||
final response = await APIRequest(
|
||||
uri: "user/search",
|
||||
needLogin: true,
|
||||
args: {
|
||||
"query": query
|
||||
}
|
||||
).exec();
|
||||
uri: "user/search", needLogin: true, args: {"query": query}).exec();
|
||||
|
||||
if (response.code != 200) return null;
|
||||
|
||||
return await UsersHelper().getUsersInfo(
|
||||
listToIntList(response.getArray()));
|
||||
return await UsersHelper()
|
||||
.getUsersInfo(response.getArray().map((f) => cast<int>(f)).toList());
|
||||
}
|
||||
|
||||
}
|
||||
/// Perform a global search
|
||||
Future<SearchResultsList> globalSearch(String query) async {
|
||||
final result = await APIRequest(
|
||||
uri: "search/global", needLogin: true, args: {"query": query}).exec();
|
||||
|
||||
result.assertOk();
|
||||
|
||||
return SearchResultsList()..addAll(result.getArray().map((f) {
|
||||
switch (f["kind"]) {
|
||||
case "user":
|
||||
return SearchResult(id: f["id"], kind: SearchResultKind.USER);
|
||||
|
||||
case "group":
|
||||
return SearchResult(id: f["id"], kind: SearchResultKind.GROUP);
|
||||
|
||||
default:
|
||||
throw Exception("Unkown search kind: ${f["kind"]}");
|
||||
}
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
196
lib/helpers/settings_helper.dart
Normal file
@ -0,0 +1,196 @@
|
||||
import 'package:comunic/enums/user_page_visibility.dart';
|
||||
import 'package:comunic/models/account_image_settings.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/general_settings.dart';
|
||||
import 'package:comunic/models/new_emoji.dart';
|
||||
import 'package:comunic/models/security_settings.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
/// Settings helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
const _APIAccountImageVisibilityAPILevels = {
|
||||
"open": AccountImageVisibilityLevels.EVERYONE,
|
||||
"public": AccountImageVisibilityLevels.COMUNIC_USERS,
|
||||
"friends": AccountImageVisibilityLevels.FRIENDS_ONLY,
|
||||
};
|
||||
|
||||
class SettingsHelper {
|
||||
/// Get & return general user settings
|
||||
static Future<GeneralSettings> getGeneralSettings() async {
|
||||
final response =
|
||||
(await APIRequest(uri: "settings/get_general", needLogin: true).exec())
|
||||
.assertOk()
|
||||
.getObject();
|
||||
|
||||
return GeneralSettings(
|
||||
email: response["email"],
|
||||
firstName: response["firstName"],
|
||||
lastName: response["lastName"],
|
||||
pageVisibility: response["is_open"]
|
||||
? UserPageVisibility.OPEN
|
||||
: response["is_public"]
|
||||
? UserPageVisibility.PUBLIC
|
||||
: UserPageVisibility.PRIVATE,
|
||||
allowComments: response["allow_comments"],
|
||||
allowPostsFromFriends: response["allow_posts_from_friends"],
|
||||
allowComunicEmails: response["allow_comunic_mails"],
|
||||
publicFriendsList: response["public_friends_list"],
|
||||
virtualDirectory: response["virtual_directory"],
|
||||
personalWebsite: response["personnal_website"],
|
||||
publicNote: response["publicNote"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Apply new general settings
|
||||
static Future<void> updateGeneralSettings(GeneralSettings settings) async {
|
||||
(await APIRequest(uri: "settings/set_general", needLogin: true, args: {
|
||||
"firstName": settings.firstName,
|
||||
"lastName": settings.lastName,
|
||||
"allow_comunic_mails": settings.allowComunicEmails ? "true" : "false",
|
||||
"isPublic": settings.pageVisibility != UserPageVisibility.PRIVATE
|
||||
? "true"
|
||||
: "false",
|
||||
"isOpen":
|
||||
settings.pageVisibility == UserPageVisibility.OPEN ? "true" : "false",
|
||||
"allowComments": settings.allowComments ? "true" : "false",
|
||||
"allowPostsFromFriends":
|
||||
settings.allowPostsFromFriends ? "true" : "false",
|
||||
"publicFriendsList": settings.publicFriendsList ? "true" : "false",
|
||||
"personnalWebsite": settings.personalWebsite,
|
||||
"virtualDirectory": settings.virtualDirectory,
|
||||
"publicNote": settings.publicNote,
|
||||
}).exec())
|
||||
.assertOk();
|
||||
}
|
||||
|
||||
/// Check out whether a virtual directory is available for a user or not
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> checkUserDirectoryAvailability(String dir) async =>
|
||||
(await APIRequest(
|
||||
uri: "settings/check_user_directory_availability",
|
||||
needLogin: true,
|
||||
args: {"directory": dir}).exec())
|
||||
.assertOk();
|
||||
|
||||
/// Get & return account image settings
|
||||
static Future<AccountImageSettings> getAccountImageSettings() async {
|
||||
final response =
|
||||
(await APIRequest(uri: "settings/get_account_image", needLogin: true)
|
||||
.exec())
|
||||
.assertOk()
|
||||
.getObject();
|
||||
|
||||
return AccountImageSettings(
|
||||
hasImage: response["has_image"],
|
||||
imageURL: response["image_url"],
|
||||
visibility:
|
||||
_APIAccountImageVisibilityAPILevels[response["visibility"]]);
|
||||
}
|
||||
|
||||
/// Upload a new account image
|
||||
static Future<bool> uploadAccountImage(PickedFile newImage) async =>
|
||||
(await APIRequest(uri: "settings/upload_account_image", needLogin: true)
|
||||
.addPickedFile("picture", newImage)
|
||||
.execWithFiles())
|
||||
.isOK;
|
||||
|
||||
/// Upload a new account image from memory
|
||||
static Future<bool> uploadAccountImageFromMemory(List<int> bytes) async =>
|
||||
(await APIRequest(uri: "settings/upload_account_image", needLogin: true)
|
||||
.addBytesFile("picture", BytesFile("accountImage.png", bytes))
|
||||
.execWithFiles())
|
||||
.isOK;
|
||||
|
||||
/// Change account image visibility level
|
||||
static Future<bool> setAccountImageVisibilityLevel(
|
||||
AccountImageVisibilityLevels level) async =>
|
||||
(await APIRequest(
|
||||
uri: "settings/set_account_image_visibility", needLogin: true)
|
||||
.addString(
|
||||
"visibility",
|
||||
|
||||
// Find appropriate visibility level
|
||||
_APIAccountImageVisibilityAPILevels.entries
|
||||
.firstWhere((f) => f.value == level)
|
||||
.key)
|
||||
.exec())
|
||||
.isOK;
|
||||
|
||||
/// Delete user account image
|
||||
static Future<bool> deleteAccountImage() async =>
|
||||
(await APIRequest(uri: "settings/delete_account_image", needLogin: true)
|
||||
.exec())
|
||||
.isOK;
|
||||
|
||||
/// Upload a new custom emoji
|
||||
static Future<void> uploadNewCustomEmoji(NewEmoji newEmoji) async =>
|
||||
(await APIRequest(
|
||||
uri: "settings/upload_custom_emoji",
|
||||
needLogin: true,
|
||||
args: {"shortcut": newEmoji.shortcut})
|
||||
.addPickedFile("image", newEmoji.image)
|
||||
.execWithFiles())
|
||||
.assertOk();
|
||||
|
||||
/// Delete a custom emoji
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> deleteCustomEmoji(int emojiID) async =>
|
||||
(await APIRequest(uri: "settings/delete_custom_emoji", needLogin: true)
|
||||
.addInt("emojiID", emojiID)
|
||||
.exec())
|
||||
.assertOk();
|
||||
|
||||
/// Check current user password
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> checkUserPassword(String password) async =>
|
||||
await APIRequest(uri: "settings/check_password", needLogin: true)
|
||||
.addString("password", password)
|
||||
.execWithThrow();
|
||||
|
||||
/// Change user password
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> changePassword(
|
||||
String oldPassword, String newPassword) async =>
|
||||
await APIRequest(uri: "settings/update_password", needLogin: true)
|
||||
.addString("oldPassword", oldPassword)
|
||||
.addString("newPassword", newPassword)
|
||||
.execWithThrow();
|
||||
|
||||
/// Retrieve security settings of the user
|
||||
///
|
||||
/// This method throws in case of failure
|
||||
static Future<SecuritySettings> getSecuritySettings(String password) async {
|
||||
final response =
|
||||
(await APIRequest(uri: "settings/get_security", needLogin: true)
|
||||
.addString("password", password)
|
||||
.execWithThrow())
|
||||
.getObject();
|
||||
|
||||
return SecuritySettings(
|
||||
securityQuestion1: response["security_question_1"],
|
||||
securityAnswer1: response["security_answer_1"],
|
||||
securityQuestion2: response["security_question_2"],
|
||||
securityAnswer2: response["security_answer_2"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Apply new security settings to the user
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> setSecuritySettings(
|
||||
String password, SecuritySettings newSettings) async {
|
||||
await APIRequest(uri: "settings/set_security", needLogin: true)
|
||||
.addString("password", password)
|
||||
.addString("security_question_1", newSettings.securityQuestion1)
|
||||
.addString("security_answer_1", newSettings.securityAnswer1)
|
||||
.addString("security_question_2", newSettings.securityQuestion2)
|
||||
.addString("security_answer_2", newSettings.securityAnswer2)
|
||||
.execWithThrow();
|
||||
}
|
||||
}
|
@ -8,6 +8,13 @@ import 'package:meta/meta.dart';
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class SurveyHelper {
|
||||
/// Get information about a single survey
|
||||
static Future<Survey> getSurveyInfo(int postID) async =>
|
||||
apiToSurvey((await APIRequest.withLogin("surveys/get_info")
|
||||
.addInt("postID", postID)
|
||||
.execWithThrow())
|
||||
.getObject());
|
||||
|
||||
/// Cancel the response of a user to a survey
|
||||
Future<bool> cancelResponse(Survey survey) async {
|
||||
return (await APIRequest(
|
||||
@ -34,6 +41,23 @@ class SurveyHelper {
|
||||
.isOK;
|
||||
}
|
||||
|
||||
/// Create a new choice in a survey
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> createNewChoice(int postID, String newChoice) async =>
|
||||
await APIRequest.withLogin("surveys/create_new_choice")
|
||||
.addInt("postID", postID)
|
||||
.addString("choice", newChoice)
|
||||
.execWithThrow();
|
||||
|
||||
/// Prevent new choices from being created on a survey
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<void> blockNewChoicesCreation(int postID) async =>
|
||||
await APIRequest.withLogin("surveys/block_new_choices_creation")
|
||||
.addInt("postID", postID)
|
||||
.execWithThrow();
|
||||
|
||||
/// Turn an API entry into a [Survey] object
|
||||
static Survey apiToSurvey(Map<String, dynamic> map) {
|
||||
// Parse survey responses
|
||||
@ -50,6 +74,7 @@ class SurveyHelper {
|
||||
question: map["question"],
|
||||
userChoice: map["user_choice"],
|
||||
choices: choices,
|
||||
allowNewChoicesCreation: map["allowNewChoices"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'package:comunic/enums/user_page_visibility.dart';
|
||||
import 'package:comunic/helpers/database/users_database_helper.dart';
|
||||
import 'package:comunic/lists/custom_emojies_list.dart';
|
||||
import 'package:comunic/lists/users_list.dart';
|
||||
import 'package:comunic/models/advanced_user_info.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/custom_emoji.dart';
|
||||
import 'package:comunic/models/user.dart';
|
||||
|
||||
/// User helper
|
||||
@ -44,21 +46,21 @@ class UsersHelper {
|
||||
final list = UsersList();
|
||||
response.getObject().forEach(
|
||||
(k, v) => list.add(
|
||||
User(
|
||||
id: v["userID"],
|
||||
firstName: v["firstName"],
|
||||
lastName: v["lastName"],
|
||||
pageVisibility: v["publicPage"] == "false"
|
||||
User(
|
||||
id: v["userID"],
|
||||
firstName: v["firstName"],
|
||||
lastName: v["lastName"],
|
||||
pageVisibility: v["publicPage"] == "false"
|
||||
? UserPageVisibility.PRIVATE
|
||||
: (v["openPage"] == "false"
|
||||
? UserPageVisibility.PRIVATE
|
||||
: (v["openPage"] == "false"
|
||||
? UserPageVisibility.PRIVATE
|
||||
: UserPageVisibility.OPEN),
|
||||
virtualDirectory: v["virtualDirectory"] == ""
|
||||
? null
|
||||
: v["virtualDirectory"],
|
||||
accountImageURL: v["accountImage"],
|
||||
),
|
||||
),
|
||||
: UserPageVisibility.OPEN),
|
||||
virtualDirectory:
|
||||
v["virtualDirectory"] == "" ? null : v["virtualDirectory"],
|
||||
accountImageURL: v["accountImage"],
|
||||
customEmojies: _parseCustomEmojies(v["customEmojis"]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Save the list
|
||||
@ -71,7 +73,8 @@ class UsersHelper {
|
||||
/// of failure
|
||||
Future<UsersList> getListWithThrow(Set<int> users,
|
||||
{bool forceDownload = false}) async {
|
||||
final list = await getUsersInfo(users.toList());
|
||||
final list =
|
||||
await getUsersInfo(users.toList(), forceDownload: forceDownload);
|
||||
|
||||
if (list == null)
|
||||
throw Exception(
|
||||
@ -81,8 +84,10 @@ class UsersHelper {
|
||||
}
|
||||
|
||||
/// Get information about a single user. Throws in case of failure
|
||||
Future<User> getSingleWithThrow(int user) async {
|
||||
return (await getListWithThrow(Set<int>()..add(user)))[0];
|
||||
Future<User> getSingleWithThrow(int user,
|
||||
{bool forceDownload = false}) async {
|
||||
return (await getListWithThrow(Set<int>()..add(user),
|
||||
forceDownload: forceDownload))[0];
|
||||
}
|
||||
|
||||
/// Get users information from a given [Set]
|
||||
@ -154,8 +159,30 @@ class UsersHelper {
|
||||
virtualDirectory:
|
||||
data["virtualDirectory"] == "" ? null : data["virtualDirectory"],
|
||||
accountImageURL: data["accountImage"],
|
||||
customEmojies: _parseCustomEmojies(data["customEmojis"]),
|
||||
publicNote: data["publicNote"],
|
||||
canPostTexts: data["can_post_texts"],
|
||||
isFriendsListPublic: data["friend_list_public"],
|
||||
numberFriends: data["number_friends"],
|
||||
accountCreationTime: data["account_creation_time"],
|
||||
personalWebsite: data["personnalWebsite"],
|
||||
likes: data["pageLikes"],
|
||||
userLike: data["user_like_page"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse the list of custom emojies
|
||||
CustomEmojiesList _parseCustomEmojies(List<dynamic> list) {
|
||||
final l = list.cast<Map<String, dynamic>>();
|
||||
|
||||
return CustomEmojiesList()
|
||||
..addAll(l
|
||||
.map((f) => CustomEmoji(
|
||||
id: f["id"],
|
||||
userID: f["userID"],
|
||||
shortcut: f["shortcut"],
|
||||
url: f["url"],
|
||||
))
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
|
52
lib/helpers/virtual_directory_helper.dart
Normal file
@ -0,0 +1,52 @@
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Virtual directory helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
enum VirtualDirectoryType { USER, GROUP, NONE }
|
||||
|
||||
class VirtualDirectoryResult {
|
||||
final VirtualDirectoryType type;
|
||||
final int id;
|
||||
|
||||
const VirtualDirectoryResult({
|
||||
@required this.type,
|
||||
this.id,
|
||||
}) : assert(type != null);
|
||||
}
|
||||
|
||||
class VirtualDirectoryHelper {
|
||||
/// Find a virtual directory
|
||||
Future<VirtualDirectoryResult> find(String directory) async {
|
||||
final response = await APIRequest(
|
||||
uri: "virtualDirectory/find",
|
||||
needLogin: true,
|
||||
args: {"directory": directory}).exec();
|
||||
|
||||
switch (response.code) {
|
||||
case 404:
|
||||
return VirtualDirectoryResult(type: VirtualDirectoryType.NONE);
|
||||
|
||||
case 200:
|
||||
final id = response.getObject()["id"];
|
||||
final kind = response.getObject()["kind"];
|
||||
switch (kind) {
|
||||
case "user":
|
||||
return VirtualDirectoryResult(
|
||||
type: VirtualDirectoryType.USER, id: id);
|
||||
case "group":
|
||||
return VirtualDirectoryResult(
|
||||
type: VirtualDirectoryType.GROUP, id: id);
|
||||
|
||||
default:
|
||||
throw Exception("Unsupported virtual directory kind: $kind");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Could not get virtual directory!");
|
||||
}
|
||||
}
|
||||
}
|
47
lib/helpers/webapp_helper.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:comunic/helpers/conversations_helper.dart';
|
||||
import 'package:comunic/helpers/friends_helper.dart';
|
||||
import 'package:comunic/lists/memberships_list.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/membership.dart';
|
||||
|
||||
/// Web application helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class WebAppHelper {
|
||||
/// Fetch from the server the list of memberships of the user
|
||||
///
|
||||
/// Throws in case of failure
|
||||
static Future<MembershipList> getMemberships() async {
|
||||
final response =
|
||||
(await APIRequest.withLogin("webApp/getMemberships").execWithThrow())
|
||||
.getArray();
|
||||
|
||||
return MembershipList()
|
||||
..addAll(response
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(_apiToMembership)
|
||||
.where((f) => f != null)
|
||||
..toList());
|
||||
}
|
||||
|
||||
/// Turn an API entry into a membership entry
|
||||
static Membership _apiToMembership(Map<String, dynamic> entry) {
|
||||
switch (entry["type"]) {
|
||||
case "conversation":
|
||||
return Membership.conversation(
|
||||
ConversationsHelper.apiToConversation(entry["conv"]));
|
||||
|
||||
case "friend":
|
||||
return Membership.friend(FriendsHelper.apiToFriend(entry["friend"]));
|
||||
|
||||
case "group":
|
||||
return Membership.group(
|
||||
groupID: entry["id"], groupLastActive: entry["last_activity"]);
|
||||
|
||||
default:
|
||||
print("Unknown membership type: ${entry["type"]}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
235
lib/helpers/websocket_helper.dart
Normal file
@ -0,0 +1,235 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:comunic/helpers/comments_helper.dart';
|
||||
import 'package:comunic/helpers/conversations_helper.dart';
|
||||
import 'package:comunic/helpers/events_helper.dart';
|
||||
import 'package:comunic/models/api_request.dart';
|
||||
import 'package:comunic/models/config.dart';
|
||||
import 'package:comunic/models/ws_message.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// User web socket helper
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class WebSocketHelper {
|
||||
static WebSocketChannel _ws;
|
||||
|
||||
static int _counter = 0;
|
||||
|
||||
static final _requests = Map<String, Completer<dynamic>>();
|
||||
|
||||
/// Check out whether we are currently connected to WebSocket or not
|
||||
static bool isConnected() {
|
||||
return _ws != null && _ws.closeCode == null;
|
||||
}
|
||||
|
||||
/// Get WebSocket access token
|
||||
static Future<String> _getWsToken() async =>
|
||||
(await APIRequest(uri: "ws/token", needLogin: true).exec())
|
||||
.assertOk()
|
||||
.getObject()["token"];
|
||||
|
||||
/// Connect to WebSocket
|
||||
static connect() async {
|
||||
if (isConnected()) return;
|
||||
|
||||
// First, get an access token
|
||||
final token = await _getWsToken();
|
||||
|
||||
// Determine WebSocket URI
|
||||
final wsURL =
|
||||
"${(config().apiServerSecure ? "wss" : "ws")}://${config().apiServerName}${config().apiServerUri}ws?token=$token";
|
||||
final wsURI = Uri.parse(wsURL);
|
||||
|
||||
// Connect
|
||||
_ws = WebSocketChannel.connect(wsURI);
|
||||
|
||||
_ws.stream.listen(
|
||||
// When we got data
|
||||
(data) {
|
||||
print("WS New data: $data");
|
||||
_processMessage(data.toString());
|
||||
},
|
||||
|
||||
// Print errors on console
|
||||
onError: (e, stack) {
|
||||
print("WS error! $e");
|
||||
print(stack);
|
||||
},
|
||||
|
||||
// Notify when the channel is closed
|
||||
onDone: () {
|
||||
print("WS Channel closed");
|
||||
|
||||
// Clear Futures queue
|
||||
_requests.clear();
|
||||
|
||||
EventsHelper.emit(WSClosedEvent());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Close current WebSocket (if any)
|
||||
static close() {
|
||||
if (isConnected()) _ws.sink.close();
|
||||
}
|
||||
|
||||
/// Send a new message
|
||||
///
|
||||
/// This method might throw an [Exception] in case of failure
|
||||
static Future<dynamic> sendMessage(String title, dynamic data) {
|
||||
if (!isConnected())
|
||||
throw Exception("WS: Trying to send message but websocket is closed!");
|
||||
|
||||
final completer = Completer();
|
||||
|
||||
final id = "freq-${_counter++}";
|
||||
|
||||
final msg = WsMessage(id: id, title: title, data: data).toJSON();
|
||||
|
||||
print("WS Send message: $msg");
|
||||
|
||||
_ws.sink.add(msg);
|
||||
|
||||
_requests[id] = completer;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Process incoming message
|
||||
static _processMessage(String msgStr) {
|
||||
try {
|
||||
final msg = WsMessage.fromJSON(jsonDecode(msgStr));
|
||||
|
||||
if (!msg.hasId)
|
||||
_processUnattendedMessage(msg);
|
||||
else
|
||||
_respondRequests(msg);
|
||||
} catch (e, stack) {
|
||||
print("WS could not process message: $e");
|
||||
print(stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an unattended message
|
||||
static _processUnattendedMessage(WsMessage msg) {
|
||||
switch (msg.title) {
|
||||
// New number of notifications
|
||||
case "number_notifs":
|
||||
EventsHelper.emit(NewNumberNotifsEvent(msg.data));
|
||||
break;
|
||||
|
||||
// New number of unread conversations
|
||||
case "number_unread_conversations":
|
||||
EventsHelper.emit(NewNumberUnreadConversations(msg.data));
|
||||
break;
|
||||
|
||||
// New comment
|
||||
case "new_comment":
|
||||
EventsHelper.emit(
|
||||
NewCommentEvent(CommentsHelper.apiToComment(msg.data)));
|
||||
break;
|
||||
|
||||
// Updated comment
|
||||
case "comment_updated":
|
||||
EventsHelper.emit(
|
||||
UpdatedCommentEvent(CommentsHelper.apiToComment(msg.data)));
|
||||
break;
|
||||
|
||||
// Deleted comment
|
||||
case "comment_deleted":
|
||||
EventsHelper.emit(DeletedCommentEvent(msg.data));
|
||||
break;
|
||||
|
||||
// Created new conversation message
|
||||
case "new_conv_message":
|
||||
EventsHelper.emit(NewConversationMessageEvent(
|
||||
ConversationsHelper.apiToConversationMessage(msg.data)));
|
||||
break;
|
||||
|
||||
// Update conversation message content
|
||||
case "updated_conv_message":
|
||||
EventsHelper.emit(UpdatedConversationMessageEvent(
|
||||
ConversationsHelper.apiToConversationMessage(msg.data)));
|
||||
break;
|
||||
|
||||
// Deleted a conversation message
|
||||
case "deleted_conv_message":
|
||||
EventsHelper.emit(DeletedConversationMessageEvent(
|
||||
ConversationsHelper.apiToConversationMessage(msg.data)));
|
||||
break;
|
||||
|
||||
// A user joined a call
|
||||
case "user_joined_call":
|
||||
EventsHelper.emit(
|
||||
UserJoinedCallEvent(msg.data["callID"], msg.data["userID"]));
|
||||
break;
|
||||
|
||||
// A user left a call
|
||||
case "user_left_call":
|
||||
EventsHelper.emit(
|
||||
UserLeftCallEvent(msg.data["callID"], msg.data["userID"]));
|
||||
break;
|
||||
|
||||
// Got new call signal
|
||||
case "new_call_signal":
|
||||
Map<String, dynamic> signalData = msg.data["data"];
|
||||
EventsHelper.emit(NewCallSignalEvent(
|
||||
callID: msg.data["callID"],
|
||||
peerID: msg.data["peerID"],
|
||||
candidate: signalData.containsKey("candidate")
|
||||
? RTCIceCandidate(
|
||||
signalData["candidate"],
|
||||
"${signalData["sdpMLineIndex"]}" /* fix plugin crash */,
|
||||
signalData["sdpMLineIndex"])
|
||||
: null,
|
||||
sessionDescription: signalData.containsKey("type")
|
||||
? RTCSessionDescription(signalData["sdp"], signalData["type"])
|
||||
: null));
|
||||
break;
|
||||
|
||||
// Call peer ready event
|
||||
case "call_peer_ready":
|
||||
EventsHelper.emit(
|
||||
CallPeerReadyEvent(msg.data["callID"], msg.data["peerID"]));
|
||||
break;
|
||||
|
||||
// Call peer interrupted streaming event
|
||||
case "call_peer_interrupted_streaming":
|
||||
EventsHelper.emit(CallPeerInterruptedStreamingEvent(
|
||||
msg.data["callID"], msg.data["peerID"]));
|
||||
break;
|
||||
|
||||
// The call has been closed
|
||||
case "call_closed":
|
||||
EventsHelper.emit(CallClosedEvent(msg.data));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Exception("Unknown message type: ${msg.title}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Process responses to requests
|
||||
static _respondRequests(WsMessage msg) {
|
||||
if (!_requests.containsKey(msg.id))
|
||||
throw Exception(
|
||||
"Could not find request ${msg.id} in the requests queue!");
|
||||
|
||||
final completer = _requests.remove(msg.id);
|
||||
|
||||
// Handles errors
|
||||
if (msg.title != "success") {
|
||||
completer.completeError(Exception("Could not process request!"));
|
||||
return;
|
||||
}
|
||||
|
||||
completer.complete(msg.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function
|
||||
Future<dynamic> ws(String title, dynamic data) =>
|
||||
WebSocketHelper.sendMessage(title, data);
|
21
lib/lists/call_members_list.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/call_member.dart';
|
||||
|
||||
/// Call members list
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class CallMembersList extends AbstractList<CallMember> {
|
||||
/// Get the IDs of the users in this list
|
||||
Set<int> get usersID => this.map((f) => f.userID).toSet();
|
||||
|
||||
/// Remove a specific member from this list
|
||||
void removeUser(int userID) => this.removeWhere((f) => f.userID == userID);
|
||||
|
||||
/// Get the connection of a specific user
|
||||
CallMember getUser(int userID) => this.firstWhere((f) => f.userID == userID);
|
||||
|
||||
/// Extract ready peers from this list
|
||||
CallMembersList get readyPeers =>
|
||||
CallMembersList()..addAll(where((f) => f.status == MemberStatus.READY));
|
||||
}
|
@ -48,4 +48,13 @@ class ConversationMessagesList extends ListBase<ConversationMessage> {
|
||||
if (message.id < firstMessageID) firstMessageID = message.id;
|
||||
return firstMessageID;
|
||||
}
|
||||
|
||||
/// Replace a message by another one (if any)
|
||||
void replace(ConversationMessage msg) {
|
||||
final index = this.indexWhere((t) => t.id == msg.id);
|
||||
if (index >= 0) this[index] = msg;
|
||||
}
|
||||
|
||||
/// Remove a message from this list
|
||||
void removeMsg(int id) => removeWhere((f) => f.id == id);
|
||||
}
|
||||
|
21
lib/lists/custom_emojies_list.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/custom_emoji.dart';
|
||||
|
||||
/// Custom emojies list
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class CustomEmojiesList extends AbstractList<CustomEmoji> {
|
||||
/// Check if an emoji, identified by its shortcut, is present in this list
|
||||
bool hasShortcut(String shortcut) =>
|
||||
firstWhere((f) => f.shortcut == shortcut, orElse: () => null) != null;
|
||||
|
||||
/// Serialize this list
|
||||
List<Map<String, dynamic>> toSerializableList() =>
|
||||
map((f) => f.toMap()).toList();
|
||||
|
||||
/// Un-serialize this list
|
||||
static CustomEmojiesList fromSerializedList(List<dynamic> list) =>
|
||||
CustomEmojiesList()
|
||||
..addAll(list.map((f) => CustomEmoji.fromMap(f)).toList());
|
||||
}
|
11
lib/lists/group_members_list.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/group_membership.dart';
|
||||
|
||||
/// Group members list
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class GroupMembersList extends AbstractList<GroupMembership> {
|
||||
/// Get the list of users in this set
|
||||
Set<int> get usersID => map((f) => f.userID).toSet();
|
||||
}
|
@ -7,7 +7,6 @@ import 'package:comunic/models/group.dart';
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class GroupsList extends MapBase<int, Group> {
|
||||
|
||||
final Map<int, Group> _groups = Map();
|
||||
|
||||
@override
|
||||
@ -25,5 +24,5 @@ class GroupsList extends MapBase<int, Group> {
|
||||
@override
|
||||
Group remove(Object key) => _groups.remove(key);
|
||||
|
||||
Group getGroup(int id) => this[id];
|
||||
}
|
||||
|
||||
|
37
lib/lists/memberships_list.dart
Normal file
@ -0,0 +1,37 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/membership.dart';
|
||||
|
||||
/// Memberships list
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class MembershipList extends AbstractList<Membership> {
|
||||
/// Get the IDs of all the users included in some way in this list
|
||||
Set<int> get usersId {
|
||||
final s = Set<int>();
|
||||
|
||||
forEach((m) {
|
||||
switch (m.type) {
|
||||
case MembershipType.FRIEND:
|
||||
s.add(m.friend.id);
|
||||
break;
|
||||
case MembershipType.GROUP:
|
||||
break;
|
||||
case MembershipType.CONVERSATION:
|
||||
s.addAll(m.conversation.members);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// Get the ID of the groups included in this list
|
||||
Set<int> get groupsId => where((f) => f.type == MembershipType.GROUP)
|
||||
.map((f) => f.groupID)
|
||||
.toSet();
|
||||
|
||||
/// Remove a friend membership from the list
|
||||
void removeFriend(int friendID) => remove(firstWhere(
|
||||
(f) => f.type == MembershipType.FRIEND && f.friend.id == friendID));
|
||||
}
|
20
lib/lists/search_results_list.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/search_result.dart';
|
||||
|
||||
/// List of result of a global search on the database
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class SearchResultsList extends AbstractList<SearchResult> {
|
||||
/// Get the list of users included in this search result
|
||||
Set<int> get usersId => this
|
||||
.where((f) => f.kind == SearchResultKind.USER)
|
||||
.map((f) => f.id)
|
||||
.toSet();
|
||||
|
||||
/// Get the list of groups included in this search result
|
||||
Set<int> get groupsId => this
|
||||
.where((f) => f.kind == SearchResultKind.GROUP)
|
||||
.map((f) => f.id)
|
||||
.toSet();
|
||||
}
|
11
lib/lists/unread_conversations_list.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:comunic/lists/abstract_list.dart';
|
||||
import 'package:comunic/models/unread_conversation.dart';
|
||||
|
||||
/// List of unread conversations
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class UnreadConversationsList extends AbstractList<UnreadConversation> {
|
||||
/// Get the ID of the users included in this list
|
||||
Set<int> get usersID => new Set<int>()..addAll(map((f) => f.userID));
|
||||
}
|
@ -31,6 +31,9 @@ class UsersList extends ListBase<User> {
|
||||
throw "User not found in the list!";
|
||||
}
|
||||
|
||||
/// Check if the user is included in this list or not
|
||||
bool hasUser(int userID) => any((f) => f.id == userID);
|
||||
|
||||
/// Get the list of users ID present in this list
|
||||
List<int> get usersID => List.generate(length, (i) => this[i].id);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:comunic/helpers/account_helper.dart';
|
||||
import 'package:comunic/helpers/database/database_helper.dart';
|
||||
import 'package:comunic/helpers/preferences_helper.dart';
|
||||
import 'package:comunic/ui/routes/home_route.dart';
|
||||
import 'package:comunic/ui/routes/login_route.dart';
|
||||
import 'package:comunic/utils/ui_utils.dart';
|
||||
import 'package:comunic/ui/widgets/init_widget.dart';
|
||||
import 'package:comunic/utils/intl_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Main file of the application
|
||||
@ -11,58 +11,49 @@ import 'package:flutter/material.dart';
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
void subMain() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Connect to database
|
||||
await DatabaseHelper.open();
|
||||
await DatabaseHelper.cleanUpDatabase();
|
||||
|
||||
// Get current system language
|
||||
await initTranslations();
|
||||
|
||||
// Check if the user is currently signed in
|
||||
await AccountHelper().signedIn();
|
||||
|
||||
runApp(ComunicApplication(
|
||||
darkMode: (await PreferencesHelper.getInstance())
|
||||
.getBool(PreferencesKeyList.ENABLE_DARK_THEME),
|
||||
preferences: await PreferencesHelper.getInstance(),
|
||||
));
|
||||
}
|
||||
|
||||
class ComunicApplication extends StatelessWidget {
|
||||
final bool darkMode;
|
||||
class ComunicApplication extends StatefulWidget {
|
||||
final PreferencesHelper preferences;
|
||||
|
||||
const ComunicApplication({Key key, @required this.darkMode})
|
||||
: assert(darkMode != null),
|
||||
const ComunicApplication({
|
||||
Key key,
|
||||
@required this.preferences,
|
||||
}) : assert(preferences != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
ComunicApplicationState createState() => ComunicApplicationState();
|
||||
}
|
||||
|
||||
class ComunicApplicationState extends State<ComunicApplication> {
|
||||
/// Use this method to force the application to rebuild
|
||||
void refresh() => setState(() {});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final prefs = widget.preferences.preferences;
|
||||
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: ComunicApplicationHome(),
|
||||
theme: darkMode ? ThemeData.dark() : ThemeData.light(),
|
||||
home: AccountHelper.isUserIDLoaded ? InitializeWidget() : LoginRoute(),
|
||||
theme: prefs.enableDarkMode ? ThemeData.dark() : null,
|
||||
showPerformanceOverlay: prefs.showPerformancesOverlay,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ComunicApplicationHome extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ComunicApplicationHomeState();
|
||||
}
|
||||
|
||||
class _ComunicApplicationHomeState extends State<ComunicApplicationHome> {
|
||||
bool _signedIn;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
AccountHelper().signedIn().then((v) {
|
||||
setState(() {
|
||||
_signedIn = v;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_signedIn == null) return buildLoadingPage();
|
||||
|
||||
if (_signedIn)
|
||||
return HomeRoute();
|
||||
else
|
||||
return LoginRoute();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:comunic/main.dart';
|
||||
import 'package:comunic/models/config.dart';
|
||||
|
||||
@ -5,13 +7,28 @@ import 'package:comunic/models/config.dart';
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
/// Fix HTTPS issue
|
||||
class MyHttpOverride extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback = (cert, host, port) {
|
||||
return host == "devweb.local"; // Forcefully trust local website
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Config.set(Config(
|
||||
apiServerName: "devweb.local",
|
||||
apiServerUri: "/comunic/api/",
|
||||
apiServerSecure: false,
|
||||
serviceName: "ComunicFlutter",
|
||||
serviceToken: "G9sZCBmb3IgVWJ1bnR1CkNvbW1lbnRbbmVdPeCkieCkrOCkq"));
|
||||
apiServerName: "192.168.1.9:3000",
|
||||
apiServerUri: "/",
|
||||
apiServerSecure: false,
|
||||
serviceName: "ComunicFlutter",
|
||||
serviceToken: "G9sZCBmb3IgVWJ1bnR1CkNvbW1lbnRbbmVdPeCkieCkrOCkq",
|
||||
termsOfServicesURL: "http://devweb.local/comunic/current/about.php?cgu",
|
||||
));
|
||||
|
||||
HttpOverrides.global = new MyHttpOverride();
|
||||
|
||||
subMain();
|
||||
}
|
||||
|
@ -7,11 +7,13 @@ import 'package:comunic/models/config.dart';
|
||||
|
||||
void main() {
|
||||
Config.set(Config(
|
||||
apiServerName: "api.communiquons.org",
|
||||
apiServerUri: "/",
|
||||
apiServerSecure: true,
|
||||
serviceName: "ComunicFlutter",
|
||||
serviceToken: "9KfSwmB76U9UUwjXngDG7PeYccNfy"));
|
||||
apiServerName: "api.communiquons.org",
|
||||
apiServerUri: "/",
|
||||
apiServerSecure: true,
|
||||
serviceName: "ComunicFlutter",
|
||||
serviceToken: "9KfSwmB76U9UUwjXngDG7PeYccNfy",
|
||||
termsOfServicesURL: "https://about.communiquons.org/about/terms/",
|
||||
));
|
||||
|
||||
subMain();
|
||||
}
|
||||
|
21
lib/models/account_image_settings.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Account image settings
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
enum AccountImageVisibilityLevels { EVERYONE, COMUNIC_USERS, FRIENDS_ONLY }
|
||||
|
||||
class AccountImageSettings {
|
||||
final bool hasImage;
|
||||
final String imageURL;
|
||||
final AccountImageVisibilityLevels visibility;
|
||||
|
||||
const AccountImageSettings({
|
||||
@required this.hasImage,
|
||||
@required this.imageURL,
|
||||
@required this.visibility,
|
||||
}) : assert(hasImage != null),
|
||||
assert(imageURL != null),
|
||||
assert(visibility != null);
|
||||
}
|
48
lib/models/advanced_group_info.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:comunic/enums/likes_type.dart';
|
||||
import 'package:comunic/models/like_element.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'group.dart';
|
||||
|
||||
/// Advanced group information
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class AdvancedGroupInfo extends Group implements LikeElement {
|
||||
final int timeCreate;
|
||||
String description;
|
||||
String url;
|
||||
int likes;
|
||||
bool userLike;
|
||||
|
||||
AdvancedGroupInfo({
|
||||
@required int id,
|
||||
@required String name,
|
||||
@required String iconURL,
|
||||
@required int numberMembers,
|
||||
@required GroupMembershipLevel membershipLevel,
|
||||
@required GroupVisibilityLevel visibilityLevel,
|
||||
@required GroupRegistrationLevel registrationLevel,
|
||||
@required GroupPostCreationLevel postCreationLevel,
|
||||
@required String virtualDirectory,
|
||||
@required bool following,
|
||||
@required this.timeCreate,
|
||||
@required this.description,
|
||||
@required this.url,
|
||||
@required this.likes,
|
||||
@required this.userLike,
|
||||
}) : super(
|
||||
id: id,
|
||||
name: name,
|
||||
iconURL: iconURL,
|
||||
numberMembers: numberMembers,
|
||||
membershipLevel: membershipLevel,
|
||||
visibilityLevel: visibilityLevel,
|
||||
registrationLevel: registrationLevel,
|
||||
postCreationLevel: postCreationLevel,
|
||||
virtualDirectory: virtualDirectory,
|
||||
following: following);
|
||||
|
||||
@override
|
||||
LikesType likeType = LikesType.GROUP;
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
import 'package:comunic/enums/likes_type.dart';
|
||||
import 'package:comunic/enums/user_page_visibility.dart';
|
||||
import 'package:comunic/lists/custom_emojies_list.dart';
|
||||
import 'package:comunic/models/like_element.dart';
|
||||
import 'package:comunic/models/user.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@ -6,26 +9,53 @@ import 'package:meta/meta.dart';
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class AdvancedUserInfo extends User {
|
||||
class AdvancedUserInfo extends User implements LikeElement {
|
||||
final String publicNote;
|
||||
final bool canPostTexts;
|
||||
final bool isFriendsListPublic;
|
||||
final int numberFriends;
|
||||
final int accountCreationTime;
|
||||
final String personalWebsite;
|
||||
bool userLike;
|
||||
int likes;
|
||||
|
||||
const AdvancedUserInfo({
|
||||
AdvancedUserInfo({
|
||||
@required int id,
|
||||
@required String firstName,
|
||||
@required String lastName,
|
||||
@required UserPageVisibility pageVisibility,
|
||||
@required String virtualDirectory,
|
||||
@required String accountImageURL,
|
||||
@required CustomEmojiesList customEmojies,
|
||||
@required this.publicNote,
|
||||
@required this.canPostTexts,
|
||||
@required this.isFriendsListPublic,
|
||||
@required this.numberFriends,
|
||||
@required this.accountCreationTime,
|
||||
@required this.personalWebsite,
|
||||
@required this.userLike,
|
||||
@required this.likes,
|
||||
}) : assert(publicNote != null),
|
||||
assert(canPostTexts != null),
|
||||
assert(isFriendsListPublic != null),
|
||||
assert(numberFriends != null),
|
||||
assert(accountCreationTime != null),
|
||||
assert(personalWebsite != null),
|
||||
assert(userLike != null),
|
||||
assert(likes != null),
|
||||
super(
|
||||
id: id,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
pageVisibility: pageVisibility,
|
||||
virtualDirectory: virtualDirectory,
|
||||
accountImageURL: accountImageURL);
|
||||
accountImageURL: accountImageURL,
|
||||
customEmojies: customEmojies);
|
||||
|
||||
bool get hasPublicNote => publicNote.isNotEmpty;
|
||||
|
||||
bool get hasPersonalWebsite => personalWebsite.isNotEmpty;
|
||||
|
||||
@override
|
||||
LikesType get likeType => LikesType.USER;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:comunic/helpers/api_helper.dart';
|
||||
import 'package:comunic/models/api_response.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// API Request model
|
||||
@ -10,11 +12,25 @@ import 'package:meta/meta.dart';
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class BytesFile {
|
||||
final String filename;
|
||||
final List<int> bytes;
|
||||
final MediaType type;
|
||||
|
||||
const BytesFile(
|
||||
this.filename,
|
||||
this.bytes, {
|
||||
this.type,
|
||||
});
|
||||
}
|
||||
|
||||
class APIRequest {
|
||||
final String uri;
|
||||
final bool needLogin;
|
||||
Map<String, String> args;
|
||||
Map<String, File> files = Map();
|
||||
Map<String, PickedFile> pickedFiles = Map();
|
||||
Map<String, BytesFile> bytesFiles = Map();
|
||||
|
||||
APIRequest({@required this.uri, this.needLogin = false, this.args})
|
||||
: assert(uri != null),
|
||||
@ -22,18 +38,60 @@ class APIRequest {
|
||||
if (this.args == null) this.args = Map();
|
||||
}
|
||||
|
||||
void addString(String name, String value) => args[name] = value;
|
||||
APIRequest.withLogin(this.uri, {this.args})
|
||||
: needLogin = true,
|
||||
assert(uri != null) {
|
||||
if (args == null) this.args = Map();
|
||||
}
|
||||
|
||||
void addInt(String name, int value) => args[name] = value.toString();
|
||||
APIRequest.withoutLogin(this.uri, {this.args})
|
||||
: needLogin = false,
|
||||
assert(uri != null) {
|
||||
if (args == null) this.args = Map();
|
||||
}
|
||||
|
||||
void addBool(String name, bool value) =>
|
||||
args[name] = value ? "true" : "false";
|
||||
APIRequest addString(String name, String value) {
|
||||
args[name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
void addFile(String name, File file) => files[name] = file;
|
||||
APIRequest addInt(String name, int value) {
|
||||
args[name] = value.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
APIRequest addBool(String name, bool value) {
|
||||
args[name] = value ? "true" : "false";
|
||||
return this;
|
||||
}
|
||||
|
||||
APIRequest addFile(String name, File file) {
|
||||
files[name] = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
APIRequest addPickedFile(String name, PickedFile file) {
|
||||
pickedFiles[name] = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
APIRequest addBytesFile(String name, BytesFile file) {
|
||||
this.bytesFiles[name] = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
void addArgs(Map<String, String> newArgs) => args.addAll(newArgs);
|
||||
|
||||
/// Execute the request
|
||||
Future<APIResponse> exec() async => APIHelper().exec(this);
|
||||
|
||||
/// Execute the request, throws an exception in case of failure
|
||||
Future<APIResponse> execWithThrow() async => (await exec()).assertOk();
|
||||
|
||||
/// Execute the request with files
|
||||
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);
|
||||
|
||||
/// Execute the request with files to send & throws in case of failure
|
||||
Future<APIResponse> execWithFilesAndThrow() async =>
|
||||
(await execWithFiles()).assertOk();
|
||||
}
|
||||
|
@ -16,4 +16,13 @@ class APIResponse {
|
||||
|
||||
/// Check if the request is successful or not
|
||||
bool get isOK => code == 200;
|
||||
|
||||
/// Make sure the request succeed, or throw an exception else
|
||||
APIResponse assertOk() {
|
||||
|
||||
if(!this.isOK)
|
||||
throw Exception("Request failed with status $code -> $content");
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
19
lib/models/application_preferences.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
/// Application settings
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class ApplicationPreferences {
|
||||
bool enableDarkMode;
|
||||
bool forceMobileMode;
|
||||
bool showPerformancesOverlay;
|
||||
|
||||
ApplicationPreferences({
|
||||
@required this.enableDarkMode,
|
||||
@required this.forceMobileMode,
|
||||
@required this.showPerformancesOverlay,
|
||||
}) : assert(enableDarkMode != null),
|
||||
assert(forceMobileMode != null),
|
||||
assert(showPerformancesOverlay != null);
|
||||
}
|
19
lib/models/call_config.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Call configuration
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class CallConfig {
|
||||
final List<String> iceServers;
|
||||
|
||||
const CallConfig({
|
||||
@required this.iceServers,
|
||||
}) : assert(iceServers != null);
|
||||
|
||||
/// Turn this call configuration into the right for the WebRTC plugin
|
||||
Map<String, dynamic> get pluginConfig => {
|
||||
"iceServers": iceServers.map((f) => {"url": f}).toList(),
|
||||
"sdpSemantics": "unified-plan",
|
||||
};
|
||||
}
|
23
lib/models/call_member.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
|
||||
/// Single call member information
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
enum MemberStatus { JOINED, READY }
|
||||
|
||||
class CallMember {
|
||||
final int userID;
|
||||
MemberStatus status;
|
||||
MediaStream stream;
|
||||
|
||||
CallMember({
|
||||
@required this.userID,
|
||||
this.status = MemberStatus.JOINED,
|
||||
}) : assert(userID != null),
|
||||
assert(status != null);
|
||||
|
||||
bool get hasVideoStream =>
|
||||
stream != null && stream.getVideoTracks().length > 0;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import 'package:comunic/enums/likes_type.dart';
|
||||
import 'package:comunic/models/displayed_content.dart';
|
||||
import 'package:comunic/models/like_element.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:comunic/utils/account_utils.dart' as account;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Comments
|
||||
///
|
||||
@ -13,7 +15,7 @@ class Comment implements LikeElement {
|
||||
final int userID;
|
||||
final int postID;
|
||||
final int timeSent;
|
||||
String content;
|
||||
DisplayedString content;
|
||||
final String imageURL;
|
||||
int likes;
|
||||
bool userLike;
|
||||
@ -35,9 +37,13 @@ class Comment implements LikeElement {
|
||||
assert(likes != null),
|
||||
assert(userLike != null);
|
||||
|
||||
bool get hasContent => content != null && content.length > 0;
|
||||
bool get hasContent =>
|
||||
content != null && !content.isNull && content.length > 0;
|
||||
|
||||
bool get hasImage => imageURL != null;
|
||||
|
||||
bool get isOwner => userID == account.userID();
|
||||
|
||||
@override
|
||||
LikesType get likeType => LikesType.COMMENT;
|
||||
}
|
||||
|
@ -11,18 +11,21 @@ class Config {
|
||||
final bool apiServerSecure;
|
||||
final String serviceName;
|
||||
final String serviceToken;
|
||||
final String termsOfServicesURL;
|
||||
|
||||
const Config(
|
||||
{@required this.apiServerName,
|
||||
@required this.apiServerUri,
|
||||
@required this.apiServerSecure,
|
||||
@required this.serviceName,
|
||||
@required this.serviceToken})
|
||||
: assert(apiServerName != null),
|
||||
const Config({
|
||||
@required this.apiServerName,
|
||||
@required this.apiServerUri,
|
||||
@required this.apiServerSecure,
|
||||
@required this.serviceName,
|
||||
@required this.serviceToken,
|
||||
@required this.termsOfServicesURL,
|
||||
}) : assert(apiServerName != null),
|
||||
assert(apiServerUri != null),
|
||||
assert(apiServerSecure != null),
|
||||
assert(serviceName != null),
|
||||
assert(serviceToken != null);
|
||||
assert(serviceToken != null),
|
||||
assert(termsOfServicesURL != null);
|
||||
|
||||
/// Get and set static configuration
|
||||
static Config _config;
|
||||
|
@ -8,6 +8,8 @@ import 'package:meta/meta.dart';
|
||||
///
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
enum CallCapabilities { NONE, AUDIO, VIDEO }
|
||||
|
||||
class Conversation extends CacheModel implements Comparable {
|
||||
final int ownerID;
|
||||
final int lastActive;
|
||||
@ -15,6 +17,9 @@ class Conversation extends CacheModel implements Comparable {
|
||||
final bool following;
|
||||
final bool sawLastMessage;
|
||||
final List<int> members;
|
||||
final bool canEveryoneAddMembers;
|
||||
final CallCapabilities callCapabilities;
|
||||
final bool isHavingCall;
|
||||
|
||||
const Conversation({
|
||||
@required int id,
|
||||
@ -24,12 +29,18 @@ class Conversation extends CacheModel implements Comparable {
|
||||
@required this.following,
|
||||
@required this.sawLastMessage,
|
||||
@required this.members,
|
||||
@required this.canEveryoneAddMembers,
|
||||
this.callCapabilities = CallCapabilities.NONE,
|
||||
this.isHavingCall = false,
|
||||
}) : assert(id != null),
|
||||
assert(ownerID != null),
|
||||
assert(lastActive != null),
|
||||
assert(following != null),
|
||||
assert(sawLastMessage != null),
|
||||
assert(members != null),
|
||||
assert(canEveryoneAddMembers != null),
|
||||
assert(callCapabilities != null),
|
||||
assert(isHavingCall != null),
|
||||
super(id: id);
|
||||
|
||||
/// Check out whether a conversation has a fixed name or not
|
||||
@ -45,8 +56,14 @@ class Conversation extends CacheModel implements Comparable {
|
||||
name = map[ConversationTableContract.C_NAME],
|
||||
following = map[ConversationTableContract.C_FOLLOWING] == 1,
|
||||
sawLastMessage = map[ConversationTableContract.C_SAW_LAST_MESSAGE] == 1,
|
||||
members = listToIntList(
|
||||
map[ConversationTableContract.C_MEMBERS].split(",")),
|
||||
members =
|
||||
listToIntList(map[ConversationTableContract.C_MEMBERS].split(",")),
|
||||
canEveryoneAddMembers =
|
||||
map[ConversationTableContract.C_CAN_EVERYONE_ADD_MEMBERS] == 1,
|
||||
|
||||
// By default, we can not do any call
|
||||
callCapabilities = CallCapabilities.NONE,
|
||||
isHavingCall = false,
|
||||
super.fromMap(map);
|
||||
|
||||
@override
|
||||
@ -59,6 +76,8 @@ class Conversation extends CacheModel implements Comparable {
|
||||
ConversationTableContract.C_FOLLOWING: following ? 1 : 0,
|
||||
ConversationTableContract.C_SAW_LAST_MESSAGE: sawLastMessage ? 1 : 0,
|
||||
ConversationTableContract.C_MEMBERS: members.join(","),
|
||||
ConversationTableContract.C_CAN_EVERYONE_ADD_MEMBERS:
|
||||
canEveryoneAddMembers ? 1 : 0
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:comunic/helpers/database/database_contract.dart';
|
||||
import 'package:comunic/models/cache_model.dart';
|
||||
import 'package:comunic/models/displayed_content.dart';
|
||||
import 'package:comunic/utils/account_utils.dart' as account;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@ -12,7 +13,7 @@ class ConversationMessage extends CacheModel implements Comparable {
|
||||
final int conversationID;
|
||||
final int userID;
|
||||
final int timeInsert;
|
||||
final String message;
|
||||
final DisplayedString message;
|
||||
final String imageURL;
|
||||
|
||||
const ConversationMessage({
|
||||
@ -30,7 +31,7 @@ class ConversationMessage extends CacheModel implements Comparable {
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timeInsert * 1000);
|
||||
|
||||
bool get hasMessage => message != null && message.length > 0;
|
||||
bool get hasMessage => !message.isNull && message.length > 0;
|
||||
|
||||
bool get hasImage => imageURL != null && imageURL != "null";
|
||||
|
||||
@ -48,17 +49,18 @@ class ConversationMessage extends CacheModel implements Comparable {
|
||||
ConversationsMessagesTableContract.C_CONVERSATION_ID: conversationID,
|
||||
ConversationsMessagesTableContract.C_USER_ID: userID,
|
||||
ConversationsMessagesTableContract.C_TIME_INSERT: timeInsert,
|
||||
ConversationsMessagesTableContract.C_MESSAGE: message,
|
||||
ConversationsMessagesTableContract.C_MESSAGE: message.content,
|
||||
ConversationsMessagesTableContract.C_IMAGE_URL: imageURL
|
||||
};
|
||||
}
|
||||
|
||||
ConversationMessage.fromMap(Map<String, dynamic> map)
|
||||
: id = map[ConversationsMessagesTableContract.C_ID],
|
||||
conversationID = map[ConversationsMessagesTableContract.C_CONVERSATION_ID],
|
||||
conversationID =
|
||||
map[ConversationsMessagesTableContract.C_CONVERSATION_ID],
|
||||
userID = map[ConversationsMessagesTableContract.C_USER_ID],
|
||||
timeInsert = map[ConversationsMessagesTableContract.C_TIME_INSERT],
|
||||
message = map[ConversationsMessagesTableContract.C_MESSAGE],
|
||||
message = DisplayedString(map[ConversationsMessagesTableContract.C_MESSAGE]),
|
||||
imageURL = map[ConversationsMessagesTableContract.C_IMAGE_URL],
|
||||
super.fromMap(map);
|
||||
}
|
||||
|
14
lib/models/count_unread_notifications.dart
Normal file
@ -0,0 +1,14 @@
|
||||
/// Count the number of unread notifications
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class CountUnreadNotifications {
|
||||
int notifications;
|
||||
int conversations;
|
||||
|
||||
CountUnreadNotifications({
|
||||
this.notifications,
|
||||
this.conversations,
|
||||
}) : assert(notifications != null),
|
||||
assert(conversations != null);
|
||||
}
|
35
lib/models/custom_emoji.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Single custom emoji information
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class CustomEmoji {
|
||||
final int id;
|
||||
final int userID;
|
||||
final String shortcut;
|
||||
final String url;
|
||||
|
||||
const CustomEmoji({
|
||||
@required this.id,
|
||||
@required this.userID,
|
||||
@required this.shortcut,
|
||||
@required this.url,
|
||||
}) : assert(id != null),
|
||||
assert(userID != null),
|
||||
assert(shortcut != null),
|
||||
assert(url != null);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"id": id,
|
||||
"userID": userID,
|
||||
"shortcut": shortcut,
|
||||
"url": url,
|
||||
};
|
||||
|
||||
CustomEmoji.fromMap(Map<String, dynamic> map)
|
||||
: id = map["id"],
|
||||
userID = map["userID"],
|
||||
shortcut = map["shortcut"],
|
||||
url = map["url"];
|
||||
}
|
38
lib/models/displayed_content.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:comunic/utils/ui_utils.dart';
|
||||
|
||||
/// Optimized colons Emoji-parsed string
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class DisplayedString {
|
||||
String _string;
|
||||
String _parseCache;
|
||||
|
||||
DisplayedString(this._string);
|
||||
|
||||
int get length => _string.length;
|
||||
|
||||
bool get isEmpty => _string.isEmpty;
|
||||
|
||||
bool get isNull => _string == null;
|
||||
|
||||
String get content => _string;
|
||||
|
||||
set content(String content) {
|
||||
_string = content;
|
||||
_parseCache = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _string;
|
||||
}
|
||||
|
||||
String get parsedString {
|
||||
if (_parseCache == null) {
|
||||
_parseCache = parseEmojies(this._string);
|
||||
}
|
||||
|
||||
return _parseCache;
|
||||
}
|
||||
}
|
@ -8,12 +8,12 @@ import 'package:meta/meta.dart';
|
||||
/// @author Pierre HUBERT
|
||||
|
||||
class Friend extends CacheModel implements Comparable<Friend> {
|
||||
final bool accepted;
|
||||
bool accepted;
|
||||
final int lastActive;
|
||||
final bool following;
|
||||
final bool canPostTexts;
|
||||
|
||||
const Friend({
|
||||
Friend({
|
||||
@required int id,
|
||||
@required this.accepted,
|
||||
@required this.lastActive,
|
||||
|
44
lib/models/general_settings.dart
Normal file
@ -0,0 +1,44 @@
|
||||
import 'package:comunic/enums/user_page_visibility.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// General settings
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class GeneralSettings {
|
||||
final String email;
|
||||
String firstName;
|
||||
String lastName;
|
||||
UserPageVisibility pageVisibility;
|
||||
bool allowComments;
|
||||
bool allowPostsFromFriends;
|
||||
bool allowComunicEmails;
|
||||
bool publicFriendsList;
|
||||
String virtualDirectory;
|
||||
String personalWebsite;
|
||||
String publicNote;
|
||||
|
||||
GeneralSettings({
|
||||
@required this.email,
|
||||
@required this.firstName,
|
||||
@required this.lastName,
|
||||
@required this.pageVisibility,
|
||||
@required this.allowComments,
|
||||
@required this.allowPostsFromFriends,
|
||||
@required this.allowComunicEmails,
|
||||
@required this.publicFriendsList,
|
||||
@required this.virtualDirectory,
|
||||
@required this.personalWebsite,
|
||||
@required this.publicNote,
|
||||
}) : 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(virtualDirectory != null),
|
||||
assert(personalWebsite != null),
|
||||
assert(publicNote != null);
|
||||
}
|