2014-11-16 23:14:06 -08:00
|
|
|
/*
|
|
|
|
* Copyright 2014 Ahmed I. Khalil <ahmedibrahimkhali@gmail.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation; either version 2 of
|
|
|
|
* the License or (at your option) version 3 or any later version
|
|
|
|
* accepted by the membership of KDE e.V. (or its successor approved
|
|
|
|
* by the membership of KDE e.V.), which shall act as a proxy
|
|
|
|
* defined in Section 14 of version 3 of the license.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
package org.kde.kdeconnect.Plugins.MousePadPlugin;
|
|
|
|
|
2014-08-01 12:28:12 +02:00
|
|
|
import android.content.Context;
|
2014-11-09 13:27:29 -08:00
|
|
|
import android.content.SharedPreferences;
|
2014-12-21 19:13:39 -08:00
|
|
|
import android.os.Build;
|
2014-06-26 17:37:32 +00:00
|
|
|
import android.os.Bundle;
|
2014-11-09 13:27:29 -08:00
|
|
|
import android.preference.PreferenceManager;
|
2014-10-17 18:57:10 -07:00
|
|
|
import android.support.v7.app.ActionBarActivity;
|
2014-06-26 17:37:32 +00:00
|
|
|
import android.view.GestureDetector;
|
2014-12-10 23:39:28 -08:00
|
|
|
import android.view.HapticFeedbackConstants;
|
2014-12-21 19:13:39 -08:00
|
|
|
import android.view.View;
|
2014-08-01 12:28:12 +02:00
|
|
|
import android.view.inputmethod.InputMethodManager;
|
2014-06-29 18:36:23 +02:00
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuInflater;
|
|
|
|
import android.view.MenuItem;
|
2014-06-26 17:37:32 +00:00
|
|
|
import android.view.MotionEvent;
|
|
|
|
|
|
|
|
import org.kde.kdeconnect.BackgroundService;
|
|
|
|
import org.kde.kdeconnect.Device;
|
|
|
|
import org.kde.kdeconnect_tp.R;
|
|
|
|
|
2014-10-17 18:57:10 -07:00
|
|
|
public class MousePadActivity extends ActionBarActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener {
|
2014-08-14 21:40:04 +02:00
|
|
|
String deviceId;
|
|
|
|
|
2014-06-29 17:58:21 +02:00
|
|
|
private final static float MinDistanceToSendScroll = 2.5f;
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
private float mPrevX;
|
|
|
|
private float mPrevY;
|
|
|
|
private float mCurrentX;
|
|
|
|
private float mCurrentY;
|
2016-01-21 01:00:21 +06:00
|
|
|
private float mCurrentSensitivity;
|
2015-12-01 03:40:05 -08:00
|
|
|
private int scrollDirection = 1;
|
2014-06-26 17:37:32 +00:00
|
|
|
|
2014-06-29 17:15:58 +02:00
|
|
|
boolean isScrolling = false;
|
2014-06-29 17:58:21 +02:00
|
|
|
float accumulatedDistanceY = 0;
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
private GestureDetector mDetector;
|
2014-07-01 18:28:39 +02:00
|
|
|
private MousePadGestureDetector mMousePadGestureDetector;
|
|
|
|
|
2014-08-14 21:40:04 +02:00
|
|
|
KeyListenerView keyListenerView;
|
|
|
|
|
2015-08-10 00:26:58 -07:00
|
|
|
enum ClickType {
|
2015-01-10 00:31:47 -08:00
|
|
|
RIGHT, MIDDLE, NONE;
|
|
|
|
static ClickType fromString(String s) {
|
|
|
|
switch(s) {
|
|
|
|
case "right": return RIGHT;
|
|
|
|
case "middle": return MIDDLE;
|
|
|
|
default: return NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-09 13:27:29 -08:00
|
|
|
|
|
|
|
private ClickType doubleTapAction, tripleTapAction;
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
@Override
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
setContentView(R.layout.activity_mousepad);
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
deviceId = getIntent().getStringExtra("deviceId");
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-12-10 23:39:28 -08:00
|
|
|
getWindow().getDecorView().setHapticFeedbackEnabled(true);
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
mDetector = new GestureDetector(this, this);
|
2014-07-01 18:28:39 +02:00
|
|
|
mMousePadGestureDetector = new MousePadGestureDetector(this, this);
|
2014-06-26 17:37:32 +00:00
|
|
|
mDetector.setOnDoubleTapListener(this);
|
2014-08-14 21:40:04 +02:00
|
|
|
|
|
|
|
keyListenerView = (KeyListenerView)findViewById(R.id.keyListener);
|
|
|
|
keyListenerView.setDeviceId(deviceId);
|
2014-11-09 13:27:29 -08:00
|
|
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
2015-12-01 03:40:05 -08:00
|
|
|
if (prefs.getBoolean(getString(R.string.mousepad_scroll_direction),false)) {
|
|
|
|
scrollDirection = -1;
|
|
|
|
} else {
|
|
|
|
scrollDirection = 1;
|
|
|
|
}
|
2014-11-09 13:27:29 -08:00
|
|
|
String doubleTapSetting = prefs.getString(getString(R.string.mousepad_double_tap_key),
|
|
|
|
getString(R.string.mousepad_double_default));
|
|
|
|
String tripleTapSetting = prefs.getString(getString(R.string.mousepad_triple_tap_key),
|
|
|
|
getString(R.string.mousepad_triple_default));
|
2016-01-21 01:00:21 +06:00
|
|
|
String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key),
|
|
|
|
getString(R.string.mousepad_sensitivity_default));
|
2014-11-09 13:27:29 -08:00
|
|
|
|
2015-01-10 00:31:47 -08:00
|
|
|
doubleTapAction = ClickType.fromString(doubleTapSetting);
|
|
|
|
tripleTapAction = ClickType.fromString(tripleTapSetting);
|
2014-12-21 19:13:39 -08:00
|
|
|
|
2016-01-21 01:00:21 +06:00
|
|
|
switch (sensitivitySetting){
|
|
|
|
case "slowest":
|
|
|
|
mCurrentSensitivity = 0.2f;
|
|
|
|
break;
|
|
|
|
case "aboveSlowest":
|
|
|
|
mCurrentSensitivity = 0.5f;
|
|
|
|
break;
|
|
|
|
case "default":
|
|
|
|
mCurrentSensitivity = 1.0f;
|
|
|
|
break;
|
|
|
|
case "aboveDefault":
|
|
|
|
mCurrentSensitivity = 1.5f;
|
|
|
|
break;
|
|
|
|
case "fastest":
|
|
|
|
mCurrentSensitivity = 2.0f;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-12-21 19:13:39 -08:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
|
|
final View decorView = getWindow().getDecorView();
|
|
|
|
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
|
|
|
@Override
|
|
|
|
public void onSystemUiVisibilityChange(int visibility) {
|
|
|
|
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
|
|
|
|
|
|
|
int fullscreenType = 0;
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
|
|
|
fullscreenType |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
|
|
fullscreenType |= View.SYSTEM_UI_FLAG_FULLSCREEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
|
|
fullscreenType |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindow().getDecorView().setSystemUiVisibility(fullscreenType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
}
|
|
|
|
|
2014-06-29 18:36:23 +02:00
|
|
|
@Override
|
|
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
|
|
MenuInflater inflater = getMenuInflater();
|
|
|
|
inflater.inflate(R.menu.menu_mousepad, menu);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
switch (item.getItemId()) {
|
|
|
|
case R.id.menu_right_click:
|
2014-07-01 18:28:39 +02:00
|
|
|
sendRightClick();
|
|
|
|
return true;
|
2014-06-29 18:36:23 +02:00
|
|
|
case R.id.menu_middle_click:
|
2014-07-01 18:28:39 +02:00
|
|
|
sendMiddleClick();
|
|
|
|
return true;
|
2014-08-01 12:28:12 +02:00
|
|
|
case R.id.menu_show_keyboard:
|
|
|
|
showKeyboard();
|
|
|
|
return true;
|
2014-07-01 18:28:39 +02:00
|
|
|
default:
|
|
|
|
return super.onOptionsItemSelected(item);
|
2014-06-29 18:36:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
@Override
|
|
|
|
public boolean onTouchEvent(MotionEvent event) {
|
2014-07-01 18:28:39 +02:00
|
|
|
if (mMousePadGestureDetector.onTouchEvent(event)) {
|
|
|
|
return true;
|
|
|
|
}
|
2014-06-29 17:15:00 +02:00
|
|
|
if ( mDetector.onTouchEvent(event) ) {
|
|
|
|
return true;
|
|
|
|
}
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
int actionType = event.getAction();
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-29 17:15:58 +02:00
|
|
|
if (isScrolling) {
|
|
|
|
if (actionType == MotionEvent.ACTION_UP) {
|
|
|
|
isScrolling = false;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
switch (actionType) {
|
|
|
|
case MotionEvent.ACTION_DOWN:
|
2014-08-14 21:40:04 +02:00
|
|
|
mPrevX = event.getX();
|
|
|
|
mPrevY = event.getY();
|
2014-06-26 17:37:32 +00:00
|
|
|
break;
|
|
|
|
case MotionEvent.ACTION_MOVE:
|
2014-08-14 21:40:04 +02:00
|
|
|
mCurrentX = event.getX();
|
|
|
|
mCurrentY = event.getY();
|
2014-06-26 17:37:32 +00:00
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-06-26 17:37:32 +00:00
|
|
|
if (mousePadPlugin == null) return;
|
2016-01-21 01:00:21 +06:00
|
|
|
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY, mCurrentSensitivity);
|
2014-06-26 17:37:32 +00:00
|
|
|
mPrevX = mCurrentX;
|
|
|
|
mPrevY = mCurrentY;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onDown(MotionEvent e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onShowPress(MotionEvent e) {
|
2014-08-14 21:40:04 +02:00
|
|
|
//From GestureDetector, left empty
|
2014-06-26 17:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onSingleTapUp(MotionEvent e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-29 17:15:00 +02:00
|
|
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) {
|
|
|
|
// If only one thumb is used then cancel the scroll gesture
|
|
|
|
if (e2.getPointerCount() <= 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-06-29 17:58:21 +02:00
|
|
|
|
2014-06-29 17:15:58 +02:00
|
|
|
isScrolling = true;
|
2014-06-29 17:58:21 +02:00
|
|
|
|
|
|
|
accumulatedDistanceY += distanceY;
|
|
|
|
if (accumulatedDistanceY > MinDistanceToSendScroll || accumulatedDistanceY < -MinDistanceToSendScroll)
|
|
|
|
{
|
|
|
|
final float scrollToSendY = accumulatedDistanceY;
|
|
|
|
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-06-29 17:58:21 +02:00
|
|
|
if (mousePadPlugin == null) return;
|
2015-12-01 03:40:05 -08:00
|
|
|
mousePadPlugin.sendScroll(0, scrollDirection * scrollToSendY);
|
2014-06-29 17:58:21 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
accumulatedDistanceY = 0;
|
|
|
|
}
|
|
|
|
|
2014-06-29 17:15:00 +02:00
|
|
|
return true;
|
2014-06-26 17:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onLongPress(MotionEvent e) {
|
2014-12-10 23:39:28 -08:00
|
|
|
|
|
|
|
getWindow().getDecorView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
|
|
|
|
2014-12-10 23:33:13 -08:00
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-12-10 23:33:13 -08:00
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendSingleHold();
|
|
|
|
}
|
|
|
|
});
|
2014-06-26 17:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-06-26 17:37:32 +00:00
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendSingleClick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onDoubleTap(MotionEvent e) {
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-06-26 17:37:32 +00:00
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendDoubleClick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onDoubleTapEvent(MotionEvent e) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-07-01 18:28:39 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onTripleFingerTap(MotionEvent ev) {
|
2014-11-09 13:27:29 -08:00
|
|
|
switch(tripleTapAction){
|
|
|
|
case RIGHT:
|
|
|
|
sendRightClick();
|
|
|
|
break;
|
|
|
|
case MIDDLE:
|
|
|
|
sendMiddleClick();
|
|
|
|
break;
|
2015-01-10 00:31:47 -08:00
|
|
|
default:
|
2014-11-09 13:27:29 -08:00
|
|
|
}
|
2014-07-01 18:28:39 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onDoubleFingerTap(MotionEvent ev) {
|
2014-11-09 13:27:29 -08:00
|
|
|
switch(doubleTapAction){
|
|
|
|
case RIGHT:
|
|
|
|
sendRightClick();
|
|
|
|
break;
|
|
|
|
case MIDDLE:
|
|
|
|
sendMiddleClick();
|
|
|
|
break;
|
2015-01-10 00:31:47 -08:00
|
|
|
default:
|
2014-11-09 13:27:29 -08:00
|
|
|
}
|
2014-07-01 18:28:39 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-08-01 12:28:12 +02:00
|
|
|
|
2014-07-01 18:28:39 +02:00
|
|
|
private void sendMiddleClick() {
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-07-01 18:28:39 +02:00
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendMiddleClick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendRightClick() {
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
2015-06-06 00:38:51 -07:00
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
2014-07-01 18:28:39 +02:00
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendRightClick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-11-13 09:18:13 -08:00
|
|
|
|
|
|
|
private void sendSingleHold() {
|
|
|
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
|
|
|
@Override
|
|
|
|
public void onServiceStart(BackgroundService service) {
|
|
|
|
Device device = service.getDevice(deviceId);
|
|
|
|
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
|
|
|
if (mousePadPlugin == null) return;
|
|
|
|
mousePadPlugin.sendSingleHold();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2014-08-01 12:28:12 +02:00
|
|
|
|
|
|
|
private void showKeyboard() {
|
|
|
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
2014-10-17 19:10:24 -07:00
|
|
|
imm.toggleSoftInputFromWindow(keyListenerView.getWindowToken(), 0, 0);
|
2014-08-01 12:28:12 +02:00
|
|
|
}
|
2014-08-14 21:40:04 +02:00
|
|
|
|
2015-09-09 12:34:42 -07:00
|
|
|
@Override
|
|
|
|
protected void onStart() {
|
|
|
|
super.onStart();
|
|
|
|
BackgroundService.addGuiInUseCounter(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onStop() {
|
|
|
|
super.onStop();
|
|
|
|
BackgroundService.removeGuiInUseCounter(this);
|
|
|
|
}
|
|
|
|
|
2014-06-26 17:37:32 +00:00
|
|
|
}
|
2014-08-14 21:40:04 +02:00
|
|
|
|