Browse Source

Friendiqa v0.2.1

master
LubuWest 4 years ago
parent
commit
ee50729e0d
  1. 7
      CHANGELOG.md
  2. BIN
      Friendiqa_v0.2.1.apk
  3. 9
      README.md
  4. 10
      source-android/android/AndroidManifest.xml
  5. 0
      source-android/android/libcrypto.so
  6. 0
      source-android/android/libssl.so
  7. 10
      source-android/android/src/FriendiqaActivity.java
  8. 205
      source-android/android/src/QSharePathResolver.java
  9. 0
      source-android/androidnative.pri/ci/qt-android
  10. 5
      source-android/androidnative.pri/cpp/AndroidNative/priv/systemdispatcherproxy.cpp
  11. 2
      source-android/androidnative.pri/cpp/AndroidNative/priv/systemdispatcherproxy.h
  12. 10
      source-android/androidnative.pri/cpp/AndroidNative/systemdispatcher.cpp
  13. 3
      source-android/androidnative.pri/cpp/AndroidNative/systemdispatcher.h
  14. 0
      source-android/androidnative.pri/examples/androidnativeexample/android-sources/gradlew
  15. 0
      source-android/androidnative.pri/examples/androidnativeexample/res/drawable-xxhdpi/ic_menu.png
  16. 34
      source-android/androidnative.pri/java/src/androidnative/AndroidNativeActivity.java
  17. 37
      source-android/androidnative.pri/java/src/androidnative/SystemDispatcher.java
  18. 0
      source-android/androidnative.pri/tests/instrument/activity/android-sources/gradlew
  19. 0
      source-android/androidnative.pri/tests/instrument/runner/gradlew
  20. 12
      source-android/js/helper.js
  21. 5
      source-android/js/news.js
  22. 2
      source-android/js/service.js
  23. 10
      source-android/qml/configqml/ConfigTab.qml
  24. 3
      source-android/qml/configqml/OSSettingsAndroid.qml
  25. 2
      source-android/qml/contactqml/ProfileComponent.qml
  26. 10
      source-android/qml/friendiqa.qml
  27. 3
      source-android/qml/genericqml/ImagePicker.qml
  28. 5
      source-android/qml/newsqml/MessageSend.qml
  29. 44
      source-android/qml/newsqml/NewsTab.qml
  30. 30
      source-android/qml/newsqml/Newsitem.qml
  31. 25
      source-android/qml/photoqml/ImageUploadDialog.qml
  32. 48
      source-android/qml/photoqml/PhotoTab.qml
  33. 12
      source-linux/js/helper.js
  34. 5
      source-linux/js/news.js
  35. 2
      source-linux/js/service.js
  36. 10
      source-linux/qml/configqml/ConfigTab.qml
  37. 3
      source-linux/qml/configqml/OSSettingsAndroid.qml
  38. 2
      source-linux/qml/contactqml/ProfileComponent.qml
  39. 4
      source-linux/qml/friendiqa.qml
  40. 3
      source-linux/qml/genericqml/ImagePicker.qml
  41. 5
      source-linux/qml/newsqml/MessageSend.qml
  42. 46
      source-linux/qml/newsqml/NewsTab.qml
  43. 30
      source-linux/qml/newsqml/Newsitem.qml
  44. 25
      source-linux/qml/photoqml/ImageUploadDialog.qml
  45. 48
      source-linux/qml/photoqml/PhotoTab.qml

7
CHANGELOG.md

@ -51,5 +51,10 @@
* Account deletion now also removes news, image data and events from local db
# Translations #
* Italian thanks to Davide de Prisco
* Italian thanks to Davide de Prisco
## v0.2.1 ##
* Fix for [issue 4](https://github.com/LubuWest/Friendiqa/issues/4)
* Fix for Friendica [issue 4689](https://github.com/friendica/friendica/issues/4689)
* Long posts are automatically truncated
* Intents for pictures (Send one image from gallery: attach to message, send multiple images: upload to album)

BIN
Friendiqa_v0.2.1.apk

9
README.md

@ -2,7 +2,7 @@
QML based client for the Friendica Social Network.
Tabs for news (incl. Direct Messages), friends, photos and events.
OS: currently Linux and Android(4.3).
OS: currently Linux and Android (4.3 Jelly Bean).
Source code is a QtCreator project.
## Screenshots ##
@ -20,15 +20,16 @@ QML based client for the Friendica Social Network.
Currently supported:
* Shows Posts from friends, favorited messages, Direct Messages and Notifications
* Open links in external browser
*
Click on contact photo for contact details
* Click on contact photo for contact details
* Click on like text for additional contact info
* Deletion, Reposting, Answering of Posts
* Expand truncated news items
* Liking, disliking, favoriting
* Attending for event posts
* Update fetches new posts (up to last 50) since last in local DB
* More shows older posts from local DB
* Create new Message with images or direct messages, Contact/Group access rights(can be stored), smileys
* Send image from Android gallery
* Native Android image dialog
ToDo:
@ -64,7 +65,7 @@ ToDo:
# Images #
Currently supported:
* Download public and private own images to local directory
* Upload picture to album with descriptions(public)
* Upload picture to album with descriptions(public), send from gallery
* Delete own pictures and albums on client and server
* Show albums in grid, show images in album in grid and fullscreen
* Show public and private (Friendica 3.6 server required) albums and images of contacts

10
source-android/android/AndroidManifest.xml

@ -1,11 +1,17 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.friendiqa" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.2" android:versionCode="4" android:installLocation="auto">
<manifest package="org.qtproject.friendiqa" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.2.1" android:versionCode="5" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:vmSafeMode="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Friendiqa" android:icon="@drawable/friendiqa" android:logo="@drawable/friendiqa" android:theme="@android:style/Theme.Holo.Light">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="androidnative.friendiqa.FriendiqaActivity" android:label="Friendiqa" android:screenOrientation="unspecified" android:launchMode="singleTop">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="androidnative.friendiqa.FriendiqaActivity" android:label="Friendiqa" android:screenOrientation="unspecified" android:launchMode="singleInstance" android:taskAffinity="">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="friendiqa"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>

0
source-android/android/libcrypto.so

0
source-android/android/libssl.so

10
source-android/android/src/FriendiqaActivity.java

@ -2,9 +2,9 @@ package androidnative.friendiqa;
import androidnative.AndroidNativeActivity;
/**
* Created by benlau on 8/3/2017.
*/
public class FriendiqaActivity extends AndroidNativeActivity {
public FriendiqaActivity() {
@ -13,4 +13,8 @@ public class FriendiqaActivity extends AndroidNativeActivity {
QT_ANDROID_THEMES = new String[] {""};
QT_ANDROID_DEFAULT_THEME = "";
}
}

205
source-android/android/src/QSharePathResolver.java

@ -0,0 +1,205 @@
// from: https://github.com/wkh237/react-native-fetch-blob/blob/master/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
// MIT License, see: https://github.com/wkh237/react-native-fetch-blob/blob/master/LICENSE
// original copyright: Copyright (c) 2017 xeiyan@gmail.com
// src slightly modified to be used into Qt Projects: (c) 2017 ekke@ekkes-corner.org
package org.ekkescorner.utils;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.content.ContentUris;
import android.os.Environment;
import android.content.ContentResolver;
import java.io.File;
import java.io.InputStream;
import java.io.FileOutputStream;
public class QSharePathResolver {
public static String getRealPathFromURI(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// Other Providers
else{
try {
InputStream attachment = context.getContentResolver().openInputStream(uri);
if (attachment != null) {
String filename = getContentName(context.getContentResolver(), uri);
if (filename != null) {
File file = new File(context.getCacheDir(), filename);
FileOutputStream tmp = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while (attachment.read(buffer) > 0) {
tmp.write(buffer);
}
tmp.close();
attachment.close();
return file.getAbsolutePath();
}
}
} catch (Exception e) {
// TODO SIGNAL shareError()
return null;
}
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
private static String getContentName(ContentResolver resolver, Uri uri) {
Cursor cursor = resolver.query(uri, null, null, null, null);
cursor.moveToFirst();
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
if (nameIndex >= 0) {
String name = cursor.getString(nameIndex);
cursor.close();
return name;
}
cursor.close();
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
String result = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
result = cursor.getString(index);
}
}
catch (Exception ex) {
ex.printStackTrace();
return null;
}
finally {
if (cursor != null)
cursor.close();
}
return result;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

0
source-android/androidnative.pri/ci/qt-android

5
source-android/androidnative.pri/cpp/AndroidNative/priv/systemdispatcherproxy.cpp

@ -23,3 +23,8 @@ void AndroidNative::SystemDispatcherProxy::loadClass(QString className)
{
SystemDispatcher::instance()->loadClass(className);
}
void AndroidNative::SystemDispatcherProxy::setInitialized()
{
SystemDispatcher::instance()->setInitialized();
}

2
source-android/androidnative.pri/cpp/AndroidNative/priv/systemdispatcherproxy.h

@ -16,6 +16,8 @@ namespace AndroidNative {
Q_INVOKABLE void loadClass(QString className);
Q_INVOKABLE void setInitialized();
signals:
void dispatched(QString type , QVariantMap message);

10
source-android/androidnative.pri/cpp/AndroidNative/systemdispatcher.cpp

@ -332,6 +332,16 @@ void AndroidNative::SystemDispatcher::loadClass(QString javaClassName)
dispatch("androidnative.SystemDispatcher.loadClass",message);
}
void AndroidNative::SystemDispatcher::setInitialized()
{
QVariantMap message;
message["Initialized"] = true;
dispatch("androidnative.SystemDispatcher.setInitialized",message);
}
void AndroidNative::SystemDispatcher::registerNatives()
{
Q_UNUSED(registerNativesCalled);

3
source-android/androidnative.pri/cpp/AndroidNative/systemdispatcher.h

@ -29,6 +29,9 @@ namespace AndroidNative {
*/
Q_INVOKABLE void loadClass(QString javaClassName);
//inform Dispatcher about loaded Environment, Intents can be processed
Q_INVOKABLE void setInitialized();
/// Register JNI native methods. This function must be called in JNI_OnLoad. Otherwise, the messenger will not be working
static void registerNatives();

0
source-android/androidnative.pri/examples/androidnativeexample/android-sources/gradlew

0
source-android/androidnative.pri/examples/androidnativeexample/res/drawable-xxhdpi/ic_menu.png

Before

Width: 144  |  Height: 144  |  Size: 127 B

After

Width: 144  |  Height: 144  |  Size: 127 B

34
source-android/androidnative.pri/java/src/androidnative/AndroidNativeActivity.java

@ -1,5 +1,9 @@
package androidnative;
import android.content.Intent;
import android.util.Log;
import android.app.Activity;
import android.os.*;
import java.util.Map;
/** An alternative Activity class for Qt applicaiton.
@ -14,10 +18,38 @@ public class AndroidNativeActivity extends org.qtproject.qt5.android.bindings.Qt
SystemDispatcher.onActivityResult(requestCode,resultCode,data);
}
protected void onResume() {
super.onResume();
SystemDispatcher.onActivityResume();
if((getIntent().getFlags() == (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)) || (getIntent().getFlags() == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) || (getIntent().getFlags() == Intent.FLAG_ACTIVITY_NEW_TASK) || (getIntent().getFlags() == Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)) {
SystemDispatcher.onActivityResume();
} else {
Intent data = getIntent();
if ((data != null) && !(data.getBooleanExtra("used",false))){
SystemDispatcher.loadClass("androidnative.ImagePicker");
SystemDispatcher.onActivityResult(0x245285a3,Activity.RESULT_OK,data);
getIntent().replaceExtras(new Bundle());
getIntent().setAction("");
getIntent().setData(null);
getIntent().setFlags(0);
getIntent().putExtra("used", true);
} else {
SystemDispatcher.onActivityResume();
}}
}
protected void onNewIntent(Intent data) {
super.onNewIntent(data);
SystemDispatcher.loadClass("androidnative.ImagePicker");
SystemDispatcher.onActivityResult(0x245285a3,Activity.RESULT_OK,data);
getIntent().replaceExtras(new Bundle());
getIntent().setAction("");
getIntent().setData(null);
getIntent().setFlags(0);
getIntent().putExtra("used", true);
} // onNewIntent
}

37
source-android/androidnative.pri/java/src/androidnative/SystemDispatcher.java

@ -17,6 +17,9 @@ import android.util.Log;
import android.os.Handler;
import android.os.Looper;
import android.content.Intent;
//import android.content.*;
//import android.app.*;
import android.os.*;
import java.util.concurrent.Semaphore;
import java.io.StringWriter;
import java.io.PrintWriter;
@ -110,6 +113,14 @@ public class SystemDispatcher {
public static String SYSTEM_DISPATCHER_LOAD_CLASS_MESSAGE = "androidnative.SystemDispatcher.loadClass";
public static String SYSTEM_DISPATCHER_SET_INITIALIZED_MESSAGE = "androidnative.SystemDispatcher.setInitialized";
public static boolean isIntentPending=false;
public static boolean isInitialized=false;
private static Map waitingIntent;
/** A helper function to dispatch a massage when onResume is invoked in the Activity class
*/
@ -117,6 +128,7 @@ public class SystemDispatcher {
dispatch(ACTIVITY_RESUME_MESSAGE);
}
/** A helper function to dispatch a message based on the input argument fron Activity.onActivityResult
*/
public static void onActivityResult (int requestCode, int resultCode, Intent data) {
@ -126,7 +138,17 @@ public class SystemDispatcher {
message.put("resultCode",resultCode);
message.put("data",data);
if(isInitialized) {
dispatch(ACTIVITY_RESULT_MESSAGE,message);
waitingIntent=null;
isIntentPending=false;
} else { //onIntent start
waitingIntent = message;
isIntentPending = true;
}
//onIntent end
}
private static class Payload {
@ -201,6 +223,18 @@ public class SystemDispatcher {
}
}
public static void setInitialized() {
isInitialized = true;
if(isIntentPending) {
isIntentPending = false;
dispatch(ACTIVITY_RESULT_MESSAGE,waitingIntent);
}
}
public static void init() {
SystemDispatcher.addListener(new SystemDispatcher.Listener() {
public void onDispatched(String type , Map message) {
@ -210,6 +244,9 @@ public class SystemDispatcher {
String className = (String) message.get("className");
loadClass(className);
}
if (type.equals(SYSTEM_DISPATCHER_SET_INITIALIZED_MESSAGE)) {
setInitialized();
}
}
});

0
source-android/androidnative.pri/tests/instrument/activity/android-sources/gradlew

0
source-android/androidnative.pri/tests/instrument/runner/gradlew

12
source-android/js/helper.js

@ -40,11 +40,11 @@ function friendicaRequest(login,api,rootwindow,callback) {
if (xhrequest.status=200){
callback(xhrequest.responseText)
}else{
showMessage("Error","API:" +login.server+api+"\n NO RESPONSE"+xhrequest.statusText,rootwindow);
showMessage("Error","API:\n" +login.server+api+"\n NO RESPONSE"+xhrequest.statusText,rootwindow);
}
}
catch (e){
showMessage("Error", login.server+api+"\n"+e+"\n Return: "+xhrequest.responseText,rootwindow)
showMessage("Error", "API:\n" +login.server+api+"\n"+e+"\n Return: "+xhrequest.responseText,rootwindow)
}
}
}
@ -61,11 +61,11 @@ function friendicaPostRequest(login,api,data,method,rootwindow,callback) {
try{ if (xhrequest.responseText!=""){
callback(xhrequest.responseText)
}else{
showMessage("Error",api+" NO RESPONSE",rootwindow)
showMessage("Error","API:\n" +api+" NO RESPONSE",rootwindow)
callback(xhrequest.responseText)
}
}
catch (e){showMessage("Error", api+" "+e+"\n Return:"+xhrequest.responseText,rootwindow)}
catch (e){showMessage("Error", "API:\n" + +api+" "+e+"\n Return:"+xhrequest.responseText,rootwindow)}
}
}
xhrequest.open(method, login.server+api,true,login.username,Qt.atob(login.password));
@ -88,7 +88,7 @@ function friendicaWebRequest(url,rootwindow,callback) {
if (xhrequest.readyState === XMLHttpRequest.HEADERS_RECEIVED) {}
else if(xhrequest.readyState === XMLHttpRequest.DONE) {
try{callback(xhrequest.responseText)}
catch (e){showMessage("Error",url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
catch (e){showMessage("Error","API:\n" +url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
}
}
xhrequest.open("GET", url,true);
@ -101,7 +101,7 @@ function friendicaRemoteAuthRequest(login,url,c_url,rootwindow,callback) {
if (xhrequest.readyState === XMLHttpRequest.HEADERS_RECEIVED) {}
else if(xhrequest.readyState === XMLHttpRequest.DONE) {
try{callback(xhrequest.responseText)}
catch (e){showMessage("Error",url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
catch (e){showMessage("Error","Url:\n" +url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
}
}
xhrequest.open("GET", login.server+"/api/friendica/remoteauth?c_url="+c_url+"&url="+url,true,login.username,Qt.atob(login.password));

5
source-android/js/news.js

@ -38,7 +38,7 @@ function requestFriends(login,database,rootwindow,callback){
var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
db.transaction( function(tx) {
var result = tx.executeSql('UPDATE contacts SET isFriend=0 where username="'+login.username+'"')}); // clean old friends
Helperjs.friendicaRequest(login,"/api/statuses/friends", rootwindow,function (obj){
Helperjs.friendicaRequest(login,"/api/statuses/friends?count=9999", rootwindow,function (obj){
var friends=JSON.parse(obj);
for (var i=0;i<friends.length;i++){ friends[i].isFriend=1}
//try{requestProfile(login,friends,rootwindow,function(friends_profile){callback(friends_profile)})}
@ -98,6 +98,9 @@ function getFriendsTimeline(login,database,contacts,onlynew,rootwindow,callback)
var newContacts=[];
Helperjs.friendicaRequest(login,"/api/statuses/friends_timeline"+parameter, rootwindow,function (obj){
var news=JSON.parse(obj);
if (news.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),"API:\n" +login.server+"/api/statuses/friends_timeline"+parameter+"\n Return: \n"+obj,rootwindow)
}
var newContacts=findNewContacts(news,contacts);
callback(news,newContacts)
})}

2
source-android/js/service.js

@ -327,7 +327,7 @@ function cleanContacts(login,database,callback){
var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
db.transaction( function(tx) {
var oldestnewsrs= tx.executeSql('SELECT created_at FROM news WHERE username="'+login.username+'" AND messagetype=0 ORDER BY created_at ASC LIMIT 1');
var oldestnewsTime=oldestnewsrs.rows.item(0).created_at- 604800000; //contacts can be 7 days old
if (oldestnewsrs.rows.length>0){ var oldestnewsTime=oldestnewsrs.rows.item(0).created_at- 604800000;} else{var oldestnewsTime=0} //contacts can be 7 days old
//print(login.username+" älteste news: "+ oldestnewsTime);
var result = tx.executeSql('SELECT * from contacts WHERE username="'+login.username+'" AND isFriend=0 AND imageAge<'+oldestnewsTime); // check for friends
for (var i=0;i<result.rows.length;i++){

10
source-android/qml/configqml/ConfigTab.qml

@ -171,7 +171,7 @@ StackView{
onEditingFinished:{
Helperjs.friendicaWebRequest(servername.text+'/api/users/show?screen_name='+username.text,configBackground,function(obj){
var screennametest=JSON.parse(obj);
if (screennametest.status.error){
if (screennametest.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),qsTr("Nickname not registered at given server!"),configBackground);
configBackground.registeredUser=false;
}else{configBackground.registeredUser=true}
@ -226,7 +226,7 @@ StackView{
Text{
id: newsTypeField
anchors.fill: parent
text:"Timeline"
text:"Conversations"
}
MouseArea{
anchors.fill:parent
@ -289,7 +289,7 @@ StackView{
var credentials=JSON.parse(obj);
if (credentials.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),qsTr("Wrong password!"),root)
}
}
else{
filesystem.Directory=userconfig.imagestore;
filesystem.makeDir("contacts");
@ -332,7 +332,7 @@ StackView{
password.text="";
imagestore.text="";
maxNews.value=0;
newsTypeField.text="Timeline";
newsTypeField.text="Conversations";
messageIntervalSlider.value=0;
userButton.text=qsTr("User");
Helperjs.readData(db,"config","",function(storedUsers){
@ -354,7 +354,7 @@ StackView{
password.text=""
imagestore.text=""
maxNews.value=0
newsTypeField.text="Timeline"
newsTypeField.text="Conversations"
messageIntervalSlider.value=0
userButton.text=qsTr("User")
}

3
source-android/qml/configqml/OSSettingsAndroid.qml

@ -37,4 +37,7 @@ QtObject{
property int backKey: Qt.Key_Back
//property string attachImageDir:filesystem.cameraPath+"/"
property string imagePickQml: "ImagePicker"
property string imagePicker:'import QtQuick 2.0; import "qrc:/qml/genericqml";'+
imagePickQml+'{multiple : true;onReady: {attachImageURLs.push(imageUrl);'+
'attachImage(imageUrl)}}'
}

2
source-android/qml/contactqml/ProfileComponent.qml

@ -211,7 +211,7 @@ Rectangle {
Text{
id:profiletextfield
x:2*mm
y:3.5*mm
y:4.5*mm
width:parent.width-2.5*mm
wrapMode: Text.Wrap
font.pixelSize: 3*mm

10
source-android/qml/friendiqa.qml

@ -57,6 +57,7 @@ TabView{
signal friendsSignal(var username)
signal contactdetailsSignal(var contact)
signal eventSignal(var contact)
signal uploadSignal(var urls)
property var news:[]
property var newContacts:[]
@ -161,7 +162,7 @@ TabView{
title: "\uf03a"
id: newstab
property string newstabstatus
property var conversation
property var conversation:[]
source:(root.currentIndex==0)? "qrc:/qml/newsqml/NewsTab.qml":""
}
Tab{
@ -188,4 +189,11 @@ TabView{
id: configtab
source: (root.currentIndex==4)?"qrc:/qml/configqml/ConfigTab.qml":""
}
Component.onCompleted: {
var imagePicker = Qt.createQmlObject('import QtQuick 2.0; import "qrc:/qml/genericqml";'+
osSettings.imagePickQml+'{multiple : true; onReady: {'+
'if(imageUrls.length==1){root.currentIndex=0;newstab.active=true;root.uploadSignal(imageUrls)} else{'+
' root.currentIndex=2;fotostab.active=true;'+
'root.uploadSignal(imageUrls)};}}',root,"imagePicker");
}
}

3
source-android/qml/genericqml/ImagePicker.qml

@ -39,8 +39,6 @@ Item {
target: SystemDispatcher
onDispatched: {
if (type === m_CHOSEN_MESSAGE) {
//imageUrls = message.imageUrls;
//imageUrl = imageUrls[0];
var h=[];
for (var n in message.imageUrls){
h.push("file://"+ decodeURIComponent(message.imageUrls[n]).substring(5))
@ -54,6 +52,7 @@ Item {
Component.onCompleted: {
SystemDispatcher.loadClass("androidnative.ImagePicker");
if (root.currentIndex==0){SystemDispatcher.setInitialized();}
}
}

5
source-android/qml/newsqml/MessageSend.qml

@ -209,7 +209,7 @@ Flickable{
id: cancelButton
text: "\uf057"
onClicked: {newstab.newstabstatus=login.newsViewType;
newsStack.pop()}
newsStack.pop(null)}
}
BlueButton {
id: sendButton
@ -220,10 +220,11 @@ Flickable{
if (directmessage==0){
statusUpdate(title,body,messageSend.parentId,attachImageURLs)}
else {dmUpdate(title,body,"",messageSend.reply_to_user) }
newstab.newstabstatus=login.newsViewType; newsStack.pop()
newstab.newstabstatus=login.newsViewType; newsStack.pop(null)
}
}
}
}
Component.onCompleted: if(attachImageURLs.length>0){attachImage(attachImageURLs[0])}
}

44
source-android/qml/newsqml/NewsTab.qml

@ -36,6 +36,8 @@ import "qrc:/js/news.js" as Newsjs
import "qrc:/js/helper.js" as Helperjs
import "qrc:/js/service.js" as Service
//import AndroidNative 1.0
Item {
Connections{
target:newstab
@ -44,18 +46,6 @@ Item {
}
}
// Connections{
// target:root
// onCurrentContactChanged:{
// if (root.newContacts.length>0){
// if(root.currentContact<root.newContacts.length){
// downloadNotice.text= qsTr("Download profile image for ")+ root.newContacts[root.currentContact].name;
// //print(root.newContacts[root.currentContact].name)
// }
// }else{downloadNotice.text=""}
// }
// }
Connections{
target:xhr
// onError:{if (data=="contact"){downloadNotice.text=root.newContacts[root.currentContact].name+"... Error!"}}
@ -63,6 +53,8 @@ Item {
}
}
Timer {id:replytimer; interval: 1000; running: false; repeat: false
onTriggered: {
if(newstab.newstabstatus=="Conversation"){
@ -123,7 +115,11 @@ Item {
newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"reply_to_user": friend,"directmessage":1,"login":root.login}});
}
function sendUrls(urls){
if((urls.length==1)&&(newsStack.depth<2)){
newsStack.push([newslistRectangle,{item:"qrc:/qml/newsqml/MessageSend.qml",properties:{attachImageURLs:urls}}])
}
}
StackView{
id: newsStack
@ -131,6 +127,7 @@ Item {
property int conversationIndex: 0
initialItem:Rectangle {
id:newslistRectangle
y:1
color: "white"
@ -340,12 +337,23 @@ Item {
root.messageSignal.connect(onFriendsMessages);
root.directmessageSignal.connect(onDirectMessage);
root.newsSignal.connect(showNews);
root.uploadSignal.connect(sendUrls);
try{newsModel.clear()} catch(e){}
if(root.news.length>0){showNews(root.news)}
else{ newstab.newstabstatus=login.newsViewType;
if(login.newsViewType=="Timeline"){Newsjs.newsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
else{Newsjs.chatsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
//print("imageUrls "+JSON.stringify(imageUrls)+" newsstack.depth:"+newsStack.depth);
//newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{attachImageURLs:[imageUrl]}})
// var imagePicker = Qt.createQmlObject('import QtQuick 2.0; import "qrc:/qml/genericqml";'+
// osSettings.imagePickQml+'{multiple : true; onReady: {'+
// 'if(imageUrls.length==1){root.currentIndex=0;newstab.active=true;root.uploadSignal(imageUrls)} else{'+
// ' root.currentIndex=2;fotostab.active=true;'+
// 'root.uploadSignal(imageUrls)};}}',newstab,"imagePicker");
//SystemDispatcher.setInitialized();
if(root.news.length>0){showNews(root.news)}
else{ newstab.newstabstatus=login.newsViewType;
if(login.newsViewType=="Timeline"){Newsjs.newsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
else{Newsjs.chatsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
}
}
}}
}
}
}

30
source-android/qml/newsqml/Newsitem.qml

@ -34,15 +34,15 @@ import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "qrc:/js/news.js" as Newsjs
import "qrc:/js/helper.js" as Helperjs
import "qrc:/qml/genericqml"
Item {
id: newsitem
width: parent.width
height:toprow.height+friendicaActivities.height+controlrow.height+1//Math.max((itemMessage.height+topFlow.height+friendicaActivities.height+4*mm),profileImage.height+user_name.height+mm)
property int itemindex: index
property string attending: ""
property int itemindex: index
onAttendingChanged: {attendLabel.visible=true;
attendLabel.text= qsTr("attending: ")+ qsTr(attending)}
@ -145,8 +145,9 @@ Item {
textFormat: Text.RichText
text:Qt.atob(newsitemobject.statusnet_html)
width: newsitem.width-8*mm-2
height: implicitHeight
height: Math.min(implicitHeight,3/4*root.height)
wrapMode: Text.Wrap
clip:true
onLinkActivated:{
Qt.openUrlExternally(link)}
Component.onCompleted:{
@ -159,6 +160,29 @@ Item {
}
}
}
BlueButton{
width: newsitem.width-8*mm-2
height:10*mm
anchors.bottom: itemMessage.bottom
visible: itemMessage.implicitHeight>3/4*root.height
text:"\uf078"
fontColor:"grey"
border.color: "transparent"
gradient: Gradient {
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 0.5; color: "white" }
}
radius:0
onClicked: {
if (text=="\uf078"){
itemMessage.height=itemMessage.implicitHeight+10*mm;text="\uf077"
} else {
itemMessage.height=Math.min(itemMessage.implicitHeight,3/4*root.height);
text="\uf078";
newsView.positionViewAtIndex(index,ListView.Beginning);
}
}
}
}
}
}

25
source-android/qml/photoqml/ImageUploadDialog.qml

@ -64,12 +64,10 @@ Rectangle{
imageUploadModel.append({"imageUrl":url,"description":""})
}
z:2
border.color: "grey"
width: parent.width-4*mm
height:parent.height-12*mm
x:2*mm
y:10*mm
//border.color: "grey"
y:1
width:root.width-mm
height:root.height-5*mm
property string directory: ""
Connections{
@ -95,8 +93,12 @@ Rectangle{
anchors.topMargin: 1*mm
anchors.right: parent.right
anchors.rightMargin: 1*mm
spacing:mm
spacing:5*mm
Text{
font.pixelSize: 3.5*mm
font.bold: true
text:qsTr("Upload to album")
}
// BlueButton{
// id:permButton
// text: ((contact_allow.length==0)&&(contact_deny.length==0)&&(group_allow.length==0)&&(group_deny.length==0))?"\uf09c":"\uf023"
@ -109,7 +111,9 @@ Rectangle{
BlueButton{
id:closeButton
text: "\uf057"
onClicked:{imageDialog.destroy()}
onClicked:{photoStack.pop();
//imageDialog.destroy()
}
}
}
@ -250,6 +254,9 @@ Rectangle{
albumModel.append({"text":storedAlbums[n]})}
})}
catch (e){print(e)}
if(attachImageURLs.length>0){
for (var n in attachImageURLs){attachImage(attachImageURLs[n])}
}
}
// BusyIndicator{
// id: imageBusy

48
source-android/qml/photoqml/PhotoTab.qml

@ -37,7 +37,11 @@ import "qrc:/js/helper.js" as Helperjs
import "qrc:/qml/photoqml"
import "qrc:/qml/genericqml"
Rectangle {
StackView{
id: photoStack
anchors.fill:parent
initialItem:Rectangle {
id:fotorectangle
y:1
width:root.width-mm
@ -48,15 +52,15 @@ Rectangle {
property bool remoteContact: false
onNewimagesChanged:{
if(newimages.length>0){
if(fotorectangle.newimages.length>0){
//print("newimages "+JSON.stringify(newimages));
var ownimagelist=[];
Helperjs.readField("album",root.db,"imageData",root.login.username,function(albums){
for (var i=0;i<newimages.length;i++){
if(albums.indexOf(newimages[i].album)==-1){
for (var i=0;i<fotorectangle.newimages.length;i++){
if(albums.indexOf(fotorectangle.newimages[i].album)==-1){
filesystem.Directory=root.login.imagestore+"/albums";
filesystem.makeDir(newimages[i].album)}
ownimagelist.push(root.login.server+"/api/friendica/photo?scale='0'&photo_id="+newimages[i].id);
filesystem.makeDir(fotorectangle.newimages[i].album)}
ownimagelist.push(root.login.server+"/api/friendica/photo?scale='0'&photo_id="+fotorectangle.newimages[i].id);
}
})
xhr.setLogin(login.username+":"+Qt.atob(login.password));
@ -69,8 +73,8 @@ Rectangle {
}
onCurrentimagenoChanged:{
if(currentimageno==newimages.length){newImagesProgress.visible=false;showFotos(root.login,"");
newimages=[];currentimageno=0}
if(fotorectangle.currentimageno==fotorectangle.newimages.length){newImagesProgress.visible=false;showFotos(root.login,"");
fotorectangle.newimages=[];fotorectangle.currentimageno=0}
// download next image
}
@ -78,17 +82,17 @@ Rectangle {
target:xhr
onDownloadedjson:{
if(type=="picturelist"){
currentimageno=currentimageno+1
fotorectangle.currentimageno=fotorectangle.currentimageno+1
Imagejs.storeImagedata(login,db,jsonObject,fotorectangle)
}
}
onDownloaded:{
if(type=="picture"){currentimageno=currentimageno+1}
if(type=="picture"){fotorectangle.currentimageno=fotorectangle.currentimageno+1}
}
onError:{if(data=="picturelist"){
var requestid=url.substring(url.lastIndexOf("=")+1);
Imagejs.dataRequest(login,requestid,db,xhr,fotorectangle)
} else {currentimageno=currentimageno+1}
} else {fotorectangle.currentimageno=fotorectangle.currentimageno+1}
}
}
// Connections{
@ -133,6 +137,10 @@ Rectangle {
})
}
function uploadUrls(urls){
photoStack.push({item:"qrc:/qml/photoqml/ImageUploadDialog.qml",properties:{attachImageURLs:urls}})
}
ProgressBar{
id: newImagesProgress
width: 15*mm
@ -141,7 +149,7 @@ Rectangle {
anchors.right:uploadPhoto.left
anchors.rightMargin:mm
visible: false
value: currentimageno/newimages.length
value: fotorectangle.currentimageno/fotorectangle.newimages.length
}
BlueButton{
@ -152,8 +160,9 @@ Rectangle {
anchors.rightMargin:mm
text:"\uf0ee"
onClicked: {
var component = Qt.createComponent("qrc:/qml/photoqml/ImageUploadDialog.qml");
var imageUpload = component.createObject(fotorectangle);
photoStack.push({item:"qrc:/qml/photoqml/ImageUploadDialog.qml",properties:{}});
// var component = Qt.createComponent("qrc:/qml/photoqml/ImageUploadDialog.qml");
// var imageUpload = component.createObject(fotorectangle);
}}
BlueButton{
@ -168,12 +177,12 @@ Rectangle {
MenuItem {
text: qsTr("All Images")
onTriggered: {
Imagejs.requestList(root.login,root.db, false, fotostab,function(obj){newimages=obj})}
Imagejs.requestList(root.login,root.db, false, fotostab,function(obj){fotorectangle.newimages=obj})}
}
MenuItem {
text: qsTr("Only new")
onTriggered: {
Imagejs.requestList(root.login,root.db, true,fotostab,function(obj){newimages=obj})}
Imagejs.requestList(root.login,root.db, true,fotostab,function(obj){fotorectangle.newimages=obj})}
}
}
onClicked: {photoupdatemenu.popup()}
@ -260,7 +269,10 @@ Rectangle {
ListView {anchors.fill: parent; model: visualphotoModel.parts.fullscreen; interactive: false }
WorkerScript{id: photoWorker;source: "qrc:/js/photoworker.js"}
Component.onCompleted: { root.fotoSignal.connect(showFotos);
if (fotostab.phototabstatus=="Images"){showFotos(root.login,"")}
Component.onCompleted: {
root.fotoSignal.connect(showFotos);
root.uploadSignal.connect(uploadUrls);
if (fotostab.phototabstatus=="Images"){showFotos(root.login,"")}
}
}
}

12
source-linux/js/helper.js

@ -40,11 +40,11 @@ function friendicaRequest(login,api,rootwindow,callback) {
if (xhrequest.status=200){
callback(xhrequest.responseText)
}else{
showMessage("Error","API:" +login.server+api+"\n NO RESPONSE"+xhrequest.statusText,rootwindow);
showMessage("Error","API:\n" +login.server+api+"\n NO RESPONSE"+xhrequest.statusText,rootwindow);
}
}
catch (e){
showMessage("Error", login.server+api+"\n"+e+"\n Return: "+xhrequest.responseText,rootwindow)
showMessage("Error", "API:\n" +login.server+api+"\n"+e+"\n Return: "+xhrequest.responseText,rootwindow)
}
}
}
@ -61,11 +61,11 @@ function friendicaPostRequest(login,api,data,method,rootwindow,callback) {
try{ if (xhrequest.responseText!=""){
callback(xhrequest.responseText)
}else{
showMessage("Error",api+" NO RESPONSE",rootwindow)
showMessage("Error","API:\n" +api+" NO RESPONSE",rootwindow)
callback(xhrequest.responseText)
}
}
catch (e){showMessage("Error", api+" "+e+"\n Return:"+xhrequest.responseText,rootwindow)}
catch (e){showMessage("Error", "API:\n" + +api+" "+e+"\n Return:"+xhrequest.responseText,rootwindow)}
}
}
xhrequest.open(method, login.server+api,true,login.username,Qt.atob(login.password));
@ -88,7 +88,7 @@ function friendicaWebRequest(url,rootwindow,callback) {
if (xhrequest.readyState === XMLHttpRequest.HEADERS_RECEIVED) {}
else if(xhrequest.readyState === XMLHttpRequest.DONE) {
try{callback(xhrequest.responseText)}
catch (e){showMessage("Error",url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
catch (e){showMessage("Error","API:\n" +url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
}
}
xhrequest.open("GET", url,true);
@ -101,7 +101,7 @@ function friendicaRemoteAuthRequest(login,url,c_url,rootwindow,callback) {
if (xhrequest.readyState === XMLHttpRequest.HEADERS_RECEIVED) {}
else if(xhrequest.readyState === XMLHttpRequest.DONE) {
try{callback(xhrequest.responseText)}
catch (e){showMessage("Error",url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
catch (e){showMessage("Error","Url:\n" +url+" "+e+"\n Return: "+xhrequest.responseText, rootwindow)}
}
}
xhrequest.open("GET", login.server+"/api/friendica/remoteauth?c_url="+c_url+"&url="+url,true,login.username,Qt.atob(login.password));

5
source-linux/js/news.js

@ -38,7 +38,7 @@ function requestFriends(login,database,rootwindow,callback){
var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
db.transaction( function(tx) {
var result = tx.executeSql('UPDATE contacts SET isFriend=0 where username="'+login.username+'"')}); // clean old friends
Helperjs.friendicaRequest(login,"/api/statuses/friends", rootwindow,function (obj){
Helperjs.friendicaRequest(login,"/api/statuses/friends?count=9999", rootwindow,function (obj){
var friends=JSON.parse(obj);
for (var i=0;i<friends.length;i++){ friends[i].isFriend=1}
//try{requestProfile(login,friends,rootwindow,function(friends_profile){callback(friends_profile)})}
@ -98,6 +98,9 @@ function getFriendsTimeline(login,database,contacts,onlynew,rootwindow,callback)
var newContacts=[];
Helperjs.friendicaRequest(login,"/api/statuses/friends_timeline"+parameter, rootwindow,function (obj){
var news=JSON.parse(obj);
if (news.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),"API:\n" +login.server+"/api/statuses/friends_timeline"+parameter+"\n Return: \n"+obj,rootwindow)
}
var newContacts=findNewContacts(news,contacts);
callback(news,newContacts)
})}

2
source-linux/js/service.js

@ -327,7 +327,7 @@ function cleanContacts(login,database,callback){
var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
db.transaction( function(tx) {
var oldestnewsrs= tx.executeSql('SELECT created_at FROM news WHERE username="'+login.username+'" AND messagetype=0 ORDER BY created_at ASC LIMIT 1');
var oldestnewsTime=oldestnewsrs.rows.item(0).created_at- 604800000; //contacts can be 7 days old
if (oldestnewsrs.rows.length>0){ var oldestnewsTime=oldestnewsrs.rows.item(0).created_at- 604800000;} else{var oldestnewsTime=0} //contacts can be 7 days old
//print(login.username+" älteste news: "+ oldestnewsTime);
var result = tx.executeSql('SELECT * from contacts WHERE username="'+login.username+'" AND isFriend=0 AND imageAge<'+oldestnewsTime); // check for friends
for (var i=0;i<result.rows.length;i++){

10
source-linux/qml/configqml/ConfigTab.qml

@ -171,7 +171,7 @@ StackView{
onEditingFinished:{
Helperjs.friendicaWebRequest(servername.text+'/api/users/show?screen_name='+username.text,configBackground,function(obj){
var screennametest=JSON.parse(obj);
if (screennametest.status.error){
if (screennametest.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),qsTr("Nickname not registered at given server!"),configBackground);
configBackground.registeredUser=false;
}else{configBackground.registeredUser=true}
@ -226,7 +226,7 @@ StackView{
Text{
id: newsTypeField
anchors.fill: parent
text:"Timeline"
text:"Conversations"
}
MouseArea{
anchors.fill:parent
@ -289,7 +289,7 @@ StackView{
var credentials=JSON.parse(obj);
if (credentials.hasOwnProperty('status')){
Helperjs.showMessage(qsTr("Error"),qsTr("Wrong password!"),root)
}
}
else{
filesystem.Directory=userconfig.imagestore;
filesystem.makeDir("contacts");
@ -332,7 +332,7 @@ StackView{
password.text="";
imagestore.text="";
maxNews.value=0;
newsTypeField.text="Timeline";
newsTypeField.text="Conversations";
messageIntervalSlider.value=0;
userButton.text=qsTr("User");
Helperjs.readData(db,"config","",function(storedUsers){
@ -354,7 +354,7 @@ StackView{
password.text=""
imagestore.text=""
maxNews.value=0
newsTypeField.text="Timeline"
newsTypeField.text="Conversations"
messageIntervalSlider.value=0
userButton.text=qsTr("User")
}

3
source-linux/qml/configqml/OSSettingsAndroid.qml

@ -37,4 +37,7 @@ QtObject{
property int backKey: Qt.Key_Back
//property string attachImageDir:filesystem.cameraPath+"/"
property string imagePickQml: "ImagePicker"
property string imagePicker:'import QtQuick 2.0; import "qrc:/qml/genericqml";'+
imagePickQml+'{multiple : true;onReady: {attachImageURLs.push(imageUrl);'+
'attachImage(imageUrl)}}'
}

2
source-linux/qml/contactqml/ProfileComponent.qml

@ -211,7 +211,7 @@ Rectangle {
Text{
id:profiletextfield
x:2*mm
y:3.5*mm
y:4.5*mm
width:parent.width-2.5*mm
wrapMode: Text.Wrap
font.pixelSize: 3*mm

4
source-linux/qml/friendiqa.qml

@ -57,6 +57,7 @@ TabView{
signal friendsSignal(var username)
signal contactdetailsSignal(var contact)
signal eventSignal(var contact)
signal uploadSignal(var urls)
property var news:[]
property var newContacts:[]
@ -161,7 +162,7 @@ TabView{