[WIP] Added start chat fragment template, chat list recycle view (with adapter), handling new chats (in live mode). // TODO: Make starting chat

This commit is contained in:
ChronosX88 2019-03-18 20:39:53 +04:00
parent 963acc7172
commit 055f62bfec
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
28 changed files with 395 additions and 103 deletions

View File

@ -1,7 +0,0 @@
package io.github.chronosx88.influence.contracts;
import android.content.SharedPreferences;
public interface MainViewContract {
//
}

View File

@ -0,0 +1,10 @@
package io.github.chronosx88.influence.contracts.chatlist;
import java.util.List;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface ChatListLogicContract {
List<ChatEntity> loadAllChats();
void createChatBySender(ChatEntity entity);
}

View File

@ -0,0 +1,6 @@
package io.github.chronosx88.influence.contracts.chatlist;
public interface ChatListPresenterContract {
void updateChatList();
void openChat(String chatID);
}

View File

@ -0,0 +1,7 @@
package io.github.chronosx88.influence.contracts.chatlist;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
public interface ChatListViewContract {
void setRecycleAdapter(ChatListAdapter adapter);
}

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.contracts;
package io.github.chronosx88.influence.contracts.main;
public interface MainLogicContract {
void initPeer();

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.contracts;
package io.github.chronosx88.influence.contracts.main;
public interface MainPresenterContract {
void initPeer();

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.contracts.main;
public interface MainViewContract {
//
}

View File

@ -0,0 +1,50 @@
package io.github.chronosx88.influence.helpers;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatListViewHolder> {
List<ChatEntity> chatList = new ArrayList<>();
@NonNull
@Override
public ChatListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_item, parent, false);
return new ChatListViewHolder(view);
}
public void setChatList(List<ChatEntity> entities) {
chatList.clear();
chatList.addAll(entities);
}
@Override
public void onBindViewHolder(@NonNull ChatListViewHolder holder, int position) {
holder.chatName.setText(chatList.get(position).getName());
}
@Override
public int getItemCount() {
return chatList.size();
}
class ChatListViewHolder extends RecyclerView.ViewHolder {
TextView chatName;
public ChatListViewHolder(View itemView) {
super(itemView);
chatName = itemView.findViewById(R.id.chat_name);
}
}
}

View File

@ -0,0 +1,7 @@
package io.github.chronosx88.influence.helpers;
public class NetworkActions {
public static final int START_CHAT = 0x0;
public static final int NEW_MESSAGE = 0x1;
public static final int MESSAGE_SENT = 0x2;
}

View File

@ -2,12 +2,12 @@ package io.github.chronosx88.influence.helpers;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import io.github.chronosx88.influence.models.roomEntities.ChatModel;
import io.github.chronosx88.influence.models.roomEntities.MessageModel;
import io.github.chronosx88.influence.models.daos.ChatDao;
import io.github.chronosx88.influence.models.daos.MessageDao;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
@Database(entities = { MessageModel.class, ChatModel.class }, version = 1)
@Database(entities = { MessageEntity.class, ChatEntity.class }, version = 1)
public abstract class RoomHelper extends RoomDatabase {
public abstract ChatDao chatDao();
public abstract MessageDao messageDao();

View File

@ -1,10 +1,11 @@
package io.github.chronosx88.influence.helpers;
public class MessageActions {
public class UIActions {
public static final int BOOTSTRAP_NOT_SPECIFIED = 0x0;
public static final int NETWORK_ERROR = 0x1;
public static final int BOOTSTRAP_SUCCESS = 0x2;
public static final int PORT_FORWARDING_ERROR = 0x3;
public static final int RELAY_CONNECTION_ERROR = 0x4;
public static final int BOOTSTRAP_ERROR = 0x5;
public static final int NEW_CHAT = 0x6;
}

View File

@ -0,0 +1,45 @@
package io.github.chronosx88.influence.logic;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkActions;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable;
public class ChatListLogic implements ChatListLogicContract, Observer {
public ChatListLogic() {
AppHelper.getObservable().register(this, MainObservable.OTHER_ACTIONS_CHANNEL);
}
@Override
public List<ChatEntity> loadAllChats() {
return AppHelper.getChatDB().chatDao().getAllChats();
}
@Override
public void handleEvent(JSONObject object) {
try {
switch ((int) object.get("action")) {
case NetworkActions.START_CHAT: {
createChatBySender(new ChatEntity(object.getString("chatID"), object.getString("name"), ""));
AppHelper.getObservable().notifyObservers(new JSONObject().put("action", UIActions.NEW_CHAT), MainObservable.UI_ACTIONS_CHANNEL);
break;
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void createChatBySender(ChatEntity entity) {
AppHelper.getChatDB().chatDao().addChat(entity);
}
}

View File

@ -25,10 +25,10 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import io.github.chronosx88.influence.contracts.MainLogicContract;
import io.github.chronosx88.influence.contracts.main.MainLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.MessageActions;
import io.github.chronosx88.influence.helpers.StorageMVStore;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
public class MainLogic implements MainLogicContract {
@ -77,7 +77,7 @@ public class MainLogic implements MainLogicContract {
} catch (NullPointerException e) {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.BOOTSTRAP_NOT_SPECIFIED), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.BOOTSTRAP_NOT_SPECIFIED), MainObservable.UI_ACTIONS_CHANNEL);
peerDHT.shutdown();
return;
} catch (JSONException ex) {
@ -86,7 +86,7 @@ public class MainLogic implements MainLogicContract {
} catch (UnknownHostException e) {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.NETWORK_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.NETWORK_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
peerDHT.shutdown();
return;
} catch (JSONException ex) {
@ -97,7 +97,7 @@ public class MainLogic implements MainLogicContract {
if(!discoverExternalAddress()) {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.PORT_FORWARDING_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.PORT_FORWARDING_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
} catch (JSONException ex) {
ex.printStackTrace();
}
@ -106,7 +106,7 @@ public class MainLogic implements MainLogicContract {
if(!setupConnectionToRelay()) {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.RELAY_CONNECTION_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.RELAY_CONNECTION_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
return;
} catch (JSONException ex) {
ex.printStackTrace();
@ -116,7 +116,7 @@ public class MainLogic implements MainLogicContract {
if(!bootstrapPeer()) {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.BOOTSTRAP_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.BOOTSTRAP_ERROR), MainObservable.UI_ACTIONS_CHANNEL);
return;
} catch (JSONException ex) {
ex.printStackTrace();
@ -125,12 +125,13 @@ public class MainLogic implements MainLogicContract {
try {
AppHelper.getObservable().notifyObservers(new JSONObject()
.put("action", MessageActions.BOOTSTRAP_SUCCESS), MainObservable.UI_ACTIONS_CHANNEL);
.put("action", UIActions.BOOTSTRAP_SUCCESS), MainObservable.UI_ACTIONS_CHANNEL);
} catch (JSONException ex) {
ex.printStackTrace();
}
AppHelper.storePeerID(preferences.getString("peerID", null));
AppHelper.storePeerDHT(peerDHT);
setReceiveHandler();
} catch (IOException e) {
e.printStackTrace();
}
@ -179,6 +180,14 @@ public class MainLogic implements MainLogicContract {
}
}
private void setReceiveHandler() {
AppHelper.getPeerDHT().peer().objectDataReply((s, r) -> {
Log.i(LOG_TAG, "# Incoming message: " + r);
AppHelper.getObservable().notifyObservers(new JSONObject((String) r), MainObservable.OTHER_ACTIONS_CHANNEL);
return null;
});
}
@Override
public void shutdownPeer() {
peerDHT.shutdown();

View File

@ -5,19 +5,19 @@ import java.util.List;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import io.github.chronosx88.influence.models.roomEntities.ChatModel;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
@Dao
public interface ChatDao {
@Insert
void addChat(ChatModel chatModel);
void addChat(ChatEntity chatEntity);
@Query("DELETE FROM chats WHERE id = :chatID")
void deleteChat(String chatID);
@Query("SELECT * FROM chats")
List<ChatModel> getAllChats();
List<ChatEntity> getAllChats();
@Query("SELECT * FROM chats WHERE id = :chatID")
List<ChatModel> getChatByID(String chatID);
List<ChatEntity> getChatByID(String chatID);
}

View File

@ -5,12 +5,12 @@ import java.util.List;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import io.github.chronosx88.influence.models.roomEntities.MessageModel;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
@Dao
public interface MessageDao {
@Insert
void insertMessage(MessageModel chatModel);
void insertMessage(MessageEntity chatModel);
@Query("DELETE FROM messages WHERE id = :msgID")
void deleteMessage(String msgID);
@ -19,8 +19,8 @@ public interface MessageDao {
void deleteMessagesByChatID(String chatID);
@Query("SELECT * FROM messages WHERE chatID = :chatID")
List<MessageModel> getMessagesByChatID(String chatID);
List<MessageEntity> getMessagesByChatID(String chatID);
@Query("SELECT * FROM messages WHERE id = :id")
List<MessageModel> getMessageByID(String id);
List<MessageEntity> getMessageByID(String id);
}

View File

@ -0,0 +1,30 @@
package io.github.chronosx88.influence.models.roomEntities;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "chats")
public class ChatEntity {
@PrimaryKey String id;
@ColumnInfo String name;
@ColumnInfo String keyPairID;
public ChatEntity(String id, String name, String keyPairID) {
this.id = id;
this.name = name;
this.keyPairID = keyPairID;
}
public String getId() {
return id;
}
public String getKeyPairID() {
return keyPairID;
}
public String getName() {
return name;
}
}

View File

@ -1,11 +0,0 @@
package io.github.chronosx88.influence.models.roomEntities;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "chats")
public class ChatModel {
@PrimaryKey String id;
String name;
String keyPairID;
}

View File

@ -0,0 +1,43 @@
package io.github.chronosx88.influence.models.roomEntities;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "messages")
public class MessageEntity {
@PrimaryKey String id;
@ColumnInfo String chatID;
@ColumnInfo String sender;
@ColumnInfo String text;
public MessageEntity(String id, String chatID, String sender, String text) {
this.id = id;
this.chatID = chatID;
this.sender = sender;
this.text = text;
}
public String getId() {
return id;
}
public String getChatID() {
return chatID;
}
public String getSender() {
return sender;
}
public String getText() {
return text;
}
@NonNull
@Override
public String toString() {
return id + "/" + chatID + "/" + sender + "/" + text;
}
}

View File

@ -1,12 +0,0 @@
package io.github.chronosx88.influence.models.roomEntities;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "messages")
public class MessageModel {
@PrimaryKey String id;
String chatID;
String sender;
String text;
}

View File

@ -0,0 +1,31 @@
package io.github.chronosx88.influence.presenters;
import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract;
import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract;
import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.logic.ChatListLogic;
public class ChatListPresenter implements ChatListPresenterContract {
private ChatListViewContract view;
private ChatListLogicContract logic;
private ChatListAdapter chatListAdapter;
public ChatListPresenter(ChatListViewContract view) {
this.view = view;
chatListAdapter = new ChatListAdapter();
this.view.setRecycleAdapter(chatListAdapter);
this.logic = new ChatListLogic();
}
@Override
public void updateChatList() {
chatListAdapter.setChatList(logic.loadAllChats());
chatListAdapter.notifyDataSetChanged();
}
@Override
public void openChat(String chatID) {
// TODO
}
}

View File

@ -1,8 +1,8 @@
package io.github.chronosx88.influence.presenters;
import io.github.chronosx88.influence.contracts.MainLogicContract;
import io.github.chronosx88.influence.contracts.MainPresenterContract;
import io.github.chronosx88.influence.contracts.MainViewContract;
import io.github.chronosx88.influence.contracts.main.MainLogicContract;
import io.github.chronosx88.influence.contracts.main.MainPresenterContract;
import io.github.chronosx88.influence.contracts.main.MainViewContract;
import io.github.chronosx88.influence.logic.MainLogic;
public class MainPresenter implements MainPresenterContract {

View File

@ -15,13 +15,14 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.MainPresenterContract;
import io.github.chronosx88.influence.contracts.MainViewContract;
import io.github.chronosx88.influence.contracts.main.MainPresenterContract;
import io.github.chronosx88.influence.contracts.main.MainViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.MessageActions;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.presenters.MainPresenter;
import io.github.chronosx88.influence.views.fragments.ChatFragment;
import io.github.chronosx88.influence.views.fragments.ChatListFragment;
import io.github.chronosx88.influence.views.fragments.SettingsFragment;
import io.github.chronosx88.influence.views.fragments.StartChatFragment;
@ -39,7 +40,7 @@ public class MainActivity extends AppCompatActivity implements Observer, MainVie
switch (item.getItemId()) {
case R.id.action_chats:
selectedFragment = new ChatFragment();
selectedFragment = new ChatListFragment();
break;
case R.id.action_settings:
selectedFragment = new SettingsFragment();
@ -67,11 +68,11 @@ public class MainActivity extends AppCompatActivity implements Observer, MainVie
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment_container, new ChatFragment())
.replace(R.id.main_fragment_container, new ChatListFragment())
.commit();
presenter = new MainPresenter(this);
AppHelper.getObservable().register(this);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL);
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
progressDialog.setCancelable(false);
@ -84,48 +85,48 @@ public class MainActivity extends AppCompatActivity implements Observer, MainVie
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
AppHelper.getObservable().unregister(this);
AppHelper.getObservable().unregister(this, MainObservable.UI_ACTIONS_CHANNEL);
}
@Override
public void handleEvent(JSONObject object) {
try {
switch ((int) object.get("action")) {
case MessageActions.BOOTSTRAP_NOT_SPECIFIED: {
case UIActions.BOOTSTRAP_NOT_SPECIFIED: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Bootstrap-нода не указана. Прерываю подключение к сети...", Toast.LENGTH_LONG).show();
});
break;
}
case MessageActions.NETWORK_ERROR: {
case UIActions.NETWORK_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Ошибка сети. Возможно, нода недоступна, или у вас отсутствует Интернет.", Toast.LENGTH_LONG).show();
});
break;
}
case MessageActions.BOOTSTRAP_SUCCESS: {
case UIActions.BOOTSTRAP_SUCCESS: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Нода успешно запущена!", Toast.LENGTH_LONG).show();
});
break;
}
case MessageActions.PORT_FORWARDING_ERROR: {
case UIActions.PORT_FORWARDING_ERROR: {
runOnUiThread(() -> {
Toast.makeText(this, "Проблемы с пробросом портов. Возможно, у вас не настроен uPnP.", Toast.LENGTH_LONG).show();
});
break;
}
case MessageActions.BOOTSTRAP_ERROR: {
case UIActions.BOOTSTRAP_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Не удалось подключиться к бутстрап-ноде.", Toast.LENGTH_LONG).show();
});
break;
}
case MessageActions.RELAY_CONNECTION_ERROR: {
case UIActions.RELAY_CONNECTION_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Не удалось подключиться к relay-ноде.", Toast.LENGTH_LONG).show();

View File

@ -1,22 +0,0 @@
package io.github.chronosx88.influence.views.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import io.github.chronosx88.influence.R;
public class ChatFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.chat_fragment, container, false);
}
}

View File

@ -0,0 +1,72 @@
package io.github.chronosx88.influence.views.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.json.JSONException;
import org.json.JSONObject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract;
import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.presenters.ChatListPresenter;
public class ChatListFragment extends Fragment implements ChatListViewContract, Observer {
private ChatListPresenterContract presenter;
private RecyclerView chatList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new ChatListPresenter(this);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.chatlist_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
chatList = view.findViewById(R.id.chatlist_container);
presenter.updateChatList();
}
@Override
public void setRecycleAdapter(ChatListAdapter adapter) {
chatList.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
presenter.updateChatList();
}
@Override
public void handleEvent(JSONObject object) {
try {
switch (object.getInt("action")) {
case UIActions.NEW_CHAT: {
presenter.updateChatList();
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

View File

@ -5,6 +5,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import io.github.chronosx88.influence.R;
@ -19,4 +21,10 @@ public class StartChatFragment extends Fragment {
Bundle savedInstanceState) {
return inflater.inflate(R.layout.start_chat_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// TODO
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="55dp"
android:gravity="center_vertical"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/chat_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="null"
android:textSize="19sp"
android:layout_marginStart="15dp"
android:textColor="@android:color/black"/>
</LinearLayout>

View File

@ -3,11 +3,8 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chatlist_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="Temporary placeholder #1"
android:textSize="34sp"/>
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -1,14 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
<EditText
android:layout_width="346dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="Temporary placeholder #3"
android:textSize="34sp"/>
android:hint="Peer ID"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Создать чат"/>
</LinearLayout>