From 09c4b83f82cea8bcadbf08b50cfb1c4cb93d48d0 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 23 Apr 2018 15:57:48 +0200 Subject: [PATCH] Initial chat work Signed-off-by: Mario Danic --- app/build.gradle | 2 + .../java/com/nextcloud/talk/api/NcApi.java | 26 ++ .../talk/controllers/CallsListController.java | 37 +- .../talk/controllers/ChatController.java | 318 ++++++++++++++++++ .../talk/models/json/chat/ChatMessage.java | 111 ++++++ .../talk/models/json/chat/ChatOCS.java | 38 +++ .../talk/models/json/chat/ChatOverall.java | 36 ++ .../com/nextcloud/talk/utils/ApiUtils.java | 4 + .../com/nextcloud/talk/utils/TimeUtils.java | 52 +++ app/src/main/res/layout/controller_chat.xml | 55 +++ app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 1 + 12 files changed, 666 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/controllers/ChatController.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOverall.java create mode 100644 app/src/main/java/com/nextcloud/talk/utils/TimeUtils.java create mode 100644 app/src/main/res/layout/controller_chat.xml diff --git a/app/build.gradle b/app/build.gradle index 238376b7b..330d860cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -150,6 +150,8 @@ dependencies { implementation 'com.github.wooplr:Spotlight:1.2.3' + implementation 'com.github.stfalcon:chatkit:0.2.2' + implementation 'com.github.Kennyc1012:BottomSheet:2.4.0' implementation 'eu.davidea:flipview:1.1.3' testImplementation 'junit:junit:4.12' diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 30bd9bc30..d03e6d1ef 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -24,6 +24,7 @@ import android.support.annotation.Nullable; import com.nextcloud.talk.models.json.call.CallOverall; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; +import com.nextcloud.talk.models.json.chat.ChatOverall; import com.nextcloud.talk.models.json.generic.GenericOverall; import com.nextcloud.talk.models.json.generic.Status; import com.nextcloud.talk.models.json.participants.AddParticipantOverall; @@ -39,6 +40,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall; import java.util.Map; import io.reactivex.Observable; +import retrofit2.Response; import retrofit2.http.DELETE; import retrofit2.http.Field; import retrofit2.http.FieldMap; @@ -256,4 +258,28 @@ public interface NcApi { @GET Observable getCapabilities(@Header("Authorization") String authorization, @Url String url); + + /* + QueryMap items are as follows: + - "lookIntoFuture": int (0 or 1), + - "limit" : int, range 100-200, + - "timeout": used with look into future, 30 default, 60 at most + - "lastKnownMessageId", int, use one from X-Chat-Last-Given + */ + @GET + Observable> pullChatMessages(@Header("Authorization") String authorization, @Url String url, + @QueryMap Map fields); + + /* + Fieldmap items are as follows: + - "message": , + - "actorDisplayName" + */ + + @FormUrlEncoded + @PUT + Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, + @FieldMap Map fields); + + } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallsListController.java index 747ec4540..9d5f1fa59 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallsListController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallsListController.java @@ -113,7 +113,7 @@ public class CallsListController extends BaseController implements SearchView.On @BindView(R.id.fast_scroller) FastScroller fastScroller; - private UserEntity userEntity; + private UserEntity currentUser; private Disposable roomsQueryDisposable; private FlexibleAdapter adapter; private List callItems = new ArrayList<>(); @@ -144,9 +144,9 @@ public class CallsListController extends BaseController implements SearchView.On getActionBar().show(); } - userEntity = userUtils.getCurrentUser(); + currentUser = userUtils.getCurrentUser(); - if (userEntity == null && + if (currentUser == null && getParentController() != null && getParentController().getRouter() != null) { getParentController().getRouter().setRoot((RouterTransaction.with(new ServerSelectionController()) .pushChangeHandler(new HorizontalChangeHandler()) @@ -155,7 +155,7 @@ public class CallsListController extends BaseController implements SearchView.On if (adapter == null) { adapter = new FlexibleAdapter<>(callItems, getActivity(), false); - if (userEntity != null) { + if (currentUser != null) { fetchData(false); } } @@ -172,7 +172,7 @@ public class CallsListController extends BaseController implements SearchView.On getActionBar().setDisplayHomeAsUpEnabled(false); } - userEntity = userUtils.getCurrentUser(); + currentUser = userUtils.getCurrentUser(); } @@ -268,15 +268,15 @@ public class CallsListController extends BaseController implements SearchView.On callItems = new ArrayList<>(); - roomsQueryDisposable = ncApi.getRooms(ApiUtils.getCredentials(userEntity.getUsername(), - userEntity.getToken()), ApiUtils.getUrlForGetRooms(userEntity.getBaseUrl())) + roomsQueryDisposable = ncApi.getRooms(ApiUtils.getCredentials(currentUser.getUsername(), + currentUser.getToken()), ApiUtils.getUrlForGetRooms(currentUser.getBaseUrl())) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(roomsOverall -> { if (roomsOverall != null) { for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) { - callItems.add(new CallItem(roomsOverall.getOcs().getData().get(i), userEntity)); + callItems.add(new CallItem(roomsOverall.getOcs().getData().get(i), currentUser)); } adapter.updateDataSet(callItems, true); @@ -305,7 +305,7 @@ public class CallsListController extends BaseController implements SearchView.On if (getParentController() != null && getParentController().getRouter() != null) { getParentController().getRouter().pushController((RouterTransaction.with - (new WebViewLoginController(userEntity.getBaseUrl(), + (new WebViewLoginController(currentUser.getBaseUrl(), true)) .pushChangeHandler(new VerticalChangeHandler()) .popChangeHandler(new VerticalChangeHandler()))); @@ -494,18 +494,25 @@ public class CallsListController extends BaseController implements SearchView.On Room room = callItem.getModel(); Bundle bundle = new Bundle(); bundle.putString(BundleKeys.KEY_ROOM_TOKEN, callItem.getModel().getToken()); - bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(userEntity)); + bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(currentUser)); if (room.hasPassword && (room.participantType.equals(Participant.ParticipantType.GUEST) || room.participantType.equals(Participant.ParticipantType.USER_FOLLOWING_LINK))) { bundle.putInt(BundleKeys.KEY_OPERATION_CODE, 99); prepareAndShowBottomSheetWithBundle(bundle, false); } else { - overridePushHandler(new NoOpControllerChangeHandler()); - overridePopHandler(new NoOpControllerChangeHandler()); - Intent callIntent = new Intent(getActivity(), CallActivity.class); - callIntent.putExtras(bundle); - startActivity(callIntent); + if (currentUser.hasSpreedCapabilityWithName("chat-v2")) { + bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, room.getDisplayName()); + getParentController().getRouter().pushController((RouterTransaction.with(new ChatController(bundle)) + .pushChangeHandler(new HorizontalChangeHandler()) + .popChangeHandler(new HorizontalChangeHandler()))); + } else { + overridePushHandler(new NoOpControllerChangeHandler()); + overridePopHandler(new NoOpControllerChangeHandler()); + Intent callIntent = new Intent(getActivity(), CallActivity.class); + callIntent.putExtras(bundle); + startActivity(callIntent); + } } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.java b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.java new file mode 100644 index 000000000..9dbbad960 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.java @@ -0,0 +1,318 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 3 of the License, or + * at your option) any later version. + * + * 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 . + */ + +package com.nextcloud.talk.controllers; + + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import com.nextcloud.talk.R; +import com.nextcloud.talk.api.NcApi; +import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.controllers.base.BaseController; +import com.nextcloud.talk.models.database.UserEntity; +import com.nextcloud.talk.models.json.call.CallOverall; +import com.nextcloud.talk.models.json.chat.ChatMessage; +import com.nextcloud.talk.models.json.chat.ChatOverall; +import com.nextcloud.talk.utils.ApiUtils; +import com.nextcloud.talk.utils.bundle.BundleKeys; +import com.nextcloud.talk.utils.database.user.UserUtils; +import com.nextcloud.talk.utils.glide.GlideApp; +import com.stfalcon.chatkit.commons.ImageLoader; +import com.stfalcon.chatkit.messages.MessageInput; +import com.stfalcon.chatkit.messages.MessagesList; +import com.stfalcon.chatkit.messages.MessagesListAdapter; + +import org.parceler.Parcels; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import autodagger.AutoInjector; +import butterknife.BindView; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import retrofit2.Response; + +@AutoInjector(NextcloudTalkApplication.class) +public class ChatController extends BaseController implements MessagesListAdapter.OnLoadMoreListener { + @Inject + NcApi ncApi; + @Inject + UserUtils userUtils; + @BindView(R.id.input) + MessageInput messageInput; + @BindView(R.id.messagesList) + MessagesList messagesList; + + private String conversationName; + private String roomToken; + private UserEntity currentUser; + + private boolean inChat = false; + private boolean historyRead = false; + private int globalLastKnownFutureMessageId = -1; + private int globalLastKnownPastMessageId = -1; + + private MessagesListAdapter adapter; + + public ChatController(Bundle args) { + super(args); + setHasOptionsMenu(true); + this.conversationName = args.getString(BundleKeys.KEY_CONVERSATION_NAME); + this.currentUser = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_USER_ENTITY)); + this.roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN); + } + + @Override + protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { + return inflater.inflate(R.layout.controller_chat, container, false); + } + + @Override + protected void onViewBound(@NonNull View view) { + super.onViewBound(view); + NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); + + adapter = new MessagesListAdapter<>(currentUser.getUserId(), new ImageLoader() { + @Override + public void loadImage(ImageView imageView, String url) { + GlideApp.with(NextcloudTalkApplication.getSharedApplication().getApplicationContext()) + .asBitmap() + .diskCacheStrategy(DiskCacheStrategy.NONE) + .load(url) + .centerInside() + .override(imageView.getMeasuredWidth(), imageView.getMeasuredHeight()) + .apply(RequestOptions.bitmapTransform(new CircleCrop())) + .into(imageView); + } + }); + + messagesList.setAdapter(adapter); + adapter.setLoadMoreListener(this); + joinRoomWithPassword(null); + } + + @Override + protected void onAttach(@NonNull View view) { + super.onAttach(view); + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + @Override + protected String getTitle() { + return conversationName; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + inChat = false; + getRouter().popCurrentController(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onDestroy() { + inChat = false; + super.onDestroy(); + } + + private void joinRoomWithPassword(@Nullable String password) { + ncApi.joinRoom(ApiUtils.getCredentials(currentUser.getUserId(), currentUser.getToken()), ApiUtils + .getUrlForRoomParticipants(currentUser.getBaseUrl(), roomToken), password) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .retry(3) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(CallOverall callOverall) { + inChat = true; + pullChatMessages(0); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + } + + private void pullChatMessages(int lookIntoFuture) { + Map fieldMap = new HashMap<>(); + fieldMap.put("lookIntoFuture", lookIntoFuture); + fieldMap.put("limit", 2); + + int lastKnown; + if (lookIntoFuture == 1) { + lastKnown = globalLastKnownFutureMessageId; + } else { + lastKnown = globalLastKnownPastMessageId; + } + + fieldMap.put("lastKnownMessageId", lastKnown); + + if (lookIntoFuture == 1) { + ncApi.pullChatMessages(ApiUtils.getCredentials(currentUser.getUserId(), currentUser.getToken()), + ApiUtils.getUrlForChat(currentUser.getBaseUrl(), roomToken), fieldMap) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .takeWhile(observable -> inChat) + .retry(3, observable -> inChat) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(Response response) { + processMessages(response, true); + pullChatMessages(1); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + + } else { + ncApi.pullChatMessages(ApiUtils.getCredentials(currentUser.getUserId(), currentUser.getToken()), + ApiUtils.getUrlForChat(currentUser.getBaseUrl(), roomToken), fieldMap) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .retry(3, observable -> inChat) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(Response response) { + processMessages(response, false); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + } + } + + private void processMessages(Response response, boolean isFromTheFuture) { + if (response.code() == 200) { + boolean shouldForceFuture = false; + if (globalLastKnownFutureMessageId == -1) { + shouldForceFuture = true; + } + + ChatOverall chatOverall = (ChatOverall) response.body(); + List chatMessageList = chatOverall.getOcs().getData(); + + if (!isFromTheFuture) { + for (int i = 0; i < chatMessageList.size(); i++) { + chatMessageList.get(i).setBaseUrl(currentUser.getBaseUrl()); + if (globalLastKnownPastMessageId == -1 || chatMessageList.get(i).getJsonMessageId() < + globalLastKnownPastMessageId) { + globalLastKnownPastMessageId = chatMessageList.get(i).getJsonMessageId(); + } + + if (shouldForceFuture) { + if (chatMessageList.get(i).getJsonMessageId() > globalLastKnownFutureMessageId) { + globalLastKnownFutureMessageId = chatMessageList.get(i).getJsonMessageId(); + } + } + } + + + adapter.addToEnd(chatMessageList, false); + + } else { + for (int i = 0; i < chatMessageList.size(); i++) { + chatMessageList.get(i).setBaseUrl(currentUser.getBaseUrl()); + if (i == chatMessageList.size() - 1) { + adapter.addToStart(chatMessageList.get(i), true); + } else { + adapter.addToStart(chatMessageList.get(i), false); + } + } + + globalLastKnownFutureMessageId = Integer.parseInt(response.headers().get("X-Chat-Last-Given")); + } + + if (shouldForceFuture) { + pullChatMessages(1); + } + } else if (response.code() == 304 && !isFromTheFuture) { + historyRead = true; + } + } + + @Override + public void onLoadMore(int page, int totalItemsCount) { + if (!historyRead) { + pullChatMessages(0); + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java new file mode 100644 index 000000000..6b4f54c66 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java @@ -0,0 +1,111 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 3 of the License, or + * at your option) any later version. + * + * 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 . + */ +package com.nextcloud.talk.models.json.chat; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; +import com.nextcloud.talk.utils.ApiUtils; +import com.nextcloud.talk.utils.TimeUtils; +import com.stfalcon.chatkit.commons.models.IMessage; +import com.stfalcon.chatkit.commons.models.IUser; + +import org.parceler.Parcel; + +import java.util.Date; + +import lombok.Data; + +@Parcel +@Data +@JsonObject +public class ChatMessage implements IMessage { + String baseUrl; + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + @JsonField(name = "id") + int jsonMessageId; + + @JsonField(name = "token") + String token; + + // guests or users + @JsonField(name = "actorType") + String actorType; + + @JsonField(name = "actorId") + String actorId; + + // send when crafting a message + @JsonField(name = "actorDisplayName") + String actorDisplayName; + + @JsonField(name = "timestamp") + long timestamp; + + // send when crafting a message, max 1000 lines + @JsonField(name = "message") + String message; + + @Override + public String getId() { + return Integer.toString(jsonMessageId); + } + + @Override + public String getText() { + return message; + } + + @Override + public IUser getUser() { + return new IUser() { + @Override + public String getId() { + return actorId; + } + + @Override + public String getName() { + return actorDisplayName; + } + + @Override + public String getAvatar() { + if ("guests".equals(actorType)) { + return null; + } else { + return ApiUtils.getUrlForAvatarWithName(getBaseUrl(), actorId, false); + } + } + }; + } + + @Override + public Date getCreatedAt() { + return TimeUtils.getDateCurrentTimeZone(timestamp); + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.java new file mode 100644 index 000000000..b39952570 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.java @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 3 of the License, or + * at your option) any later version. + * + * 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 . + */ +package com.nextcloud.talk.models.json.chat; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; +import com.nextcloud.talk.models.json.generic.GenericOCS; + +import org.parceler.Parcel; + +import java.util.List; + +import lombok.Data; + +@Data +@Parcel +@JsonObject +public class ChatOCS extends GenericOCS { + @JsonField(name = "data") + List data; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOverall.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOverall.java new file mode 100644 index 000000000..ea348d49d --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOverall.java @@ -0,0 +1,36 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 3 of the License, or + * at your option) any later version. + * + * 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 . + */ + +package com.nextcloud.talk.models.json.chat; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import org.parceler.Parcel; + +import lombok.Data; + +@Data +@Parcel +@JsonObject +public class ChatOverall { + @JsonField(name = "ocs") + ChatOCS ocs; +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 18d440fab..0b71fa5dd 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -126,6 +126,10 @@ public class ApiUtils { return getUrlForCall(baseUrl, token) + "/ping"; } + public static String getUrlForChat(String baseUrl, String token) { + return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token; + } + public static String getUrlForSignaling(String baseUrl, @Nullable String token) { String signalingUrl = baseUrl + ocsApiVersion + spreedApiVersion + "/signaling"; if (token == null) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/TimeUtils.java b/app/src/main/java/com/nextcloud/talk/utils/TimeUtils.java new file mode 100644 index 000000000..b7795bdb8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/TimeUtils.java @@ -0,0 +1,52 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 3 of the License, or + * at your option) any later version. + * + * 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 . + */ + +package com.nextcloud.talk.utils; + +import android.util.Log; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +public class TimeUtils { + private static final String TAG = "TimeUtils"; + + public static Date getDateCurrentTimeZone(long timestamp) { + try{ + Calendar calendar = Calendar.getInstance(); + TimeZone tz = Calendar.getInstance().getTimeZone(); + calendar.setTimeInMillis(timestamp * 1000); + calendar.add(Calendar.MILLISECOND, tz.getOffset(calendar.getTimeInMillis())); + Date currentTimeZone = calendar.getTime(); + return currentTimeZone; + } catch (Exception e) { + Log.d(TAG, "Failed to convert time to local timezone"); + } + return new Date(); + } + + public static String getDateStringCurrentTimeZone(long timestamp) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + return simpleDateFormat.format(getDateCurrentTimeZone(timestamp)); + } +} diff --git a/app/src/main/res/layout/controller_chat.xml b/app/src/main/res/layout/controller_chat.xml new file mode 100644 index 000000000..abe8cae6f --- /dev/null +++ b/app/src/main/res/layout/controller_chat.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 79c8e8db6..01aea2b00 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -10,5 +10,6 @@ #FFFFFF #7FC0E3 #FFEB3B + #E8E8E8 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afb9527a5..eed976687 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -164,4 +164,5 @@ Millions of users use Nextcloud daily at businesses and homes around the world. Learn more on https://nextcloud.com/talk Find Nextcloud on https://nextcloud.com + Enter a messageā€¦