├── LICENSE
├── MetaCom
├── .gitignore
├── .idea
│ ├── compiler.xml
│ ├── copyright
│ │ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── runConfigurations.xml
│ └── vcs.xml
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── metarhia
│ │ │ └── metacom
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── logback.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── metarhia
│ │ │ │ └── metacom
│ │ │ │ ├── activities
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── MainFragment.java
│ │ │ │ ├── MetaComApplication.java
│ │ │ │ ├── chat
│ │ │ │ │ ├── ChatActivity.java
│ │ │ │ │ ├── ChatFragment.java
│ │ │ │ │ └── ChatLoginFragment.java
│ │ │ │ ├── connection
│ │ │ │ │ ├── ConnectionActivity.java
│ │ │ │ │ └── ConnectionFragment.java
│ │ │ │ └── files
│ │ │ │ │ ├── DownloadFileDialog.java
│ │ │ │ │ ├── FilesFragment.java
│ │ │ │ │ └── UploadFileDialog.java
│ │ │ │ ├── connection
│ │ │ │ ├── AndroidJSTPConnection.java
│ │ │ │ ├── Errors.java
│ │ │ │ └── OkErrorHandler.java
│ │ │ │ ├── interfaces
│ │ │ │ ├── BackPressedHandler.java
│ │ │ │ ├── ChatReconnectionListener.java
│ │ │ │ ├── ConnectionCallback.java
│ │ │ │ ├── DownloadFileByCodeListener.java
│ │ │ │ ├── FileDownloadedListener.java
│ │ │ │ ├── FileUploadedCallback.java
│ │ │ │ ├── JoinRoomCallback.java
│ │ │ │ ├── LeaveRoomCallback.java
│ │ │ │ ├── MessageListener.java
│ │ │ │ └── MessageSentCallback.java
│ │ │ │ ├── models
│ │ │ │ ├── ChatRoom.java
│ │ │ │ ├── ChatRoomsManager.java
│ │ │ │ ├── ConnectionInfoProvider.java
│ │ │ │ ├── FilesManager.java
│ │ │ │ ├── Message.java
│ │ │ │ ├── MessageType.java
│ │ │ │ ├── UserConnection.java
│ │ │ │ └── UserConnectionsManager.java
│ │ │ │ └── utils
│ │ │ │ ├── Constants.java
│ │ │ │ ├── FileUtils.java
│ │ │ │ ├── HistoryCallback.java
│ │ │ │ ├── KeyboardUtils.java
│ │ │ │ ├── MainExecutor.java
│ │ │ │ ├── NetworkUtils.java
│ │ │ │ ├── PermissionUtils.java
│ │ │ │ └── TextUtils.java
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_arrow_back_24dp.xml
│ │ │ ├── ic_attach_file_24dp.xml
│ │ │ ├── ic_attach_file_grey_24dp.xml
│ │ │ ├── ic_error_24dp.xml
│ │ │ ├── ic_file_download_24dp.xml
│ │ │ ├── ic_file_download_grey_24dp.xml
│ │ │ ├── ic_file_upload_24dp.xml
│ │ │ ├── ic_file_upload_grey_24dp.xml
│ │ │ ├── ic_insert_drive_file_black_24dp.xml
│ │ │ ├── ic_mail_black_24dp.xml
│ │ │ ├── message_in.xml
│ │ │ ├── message_out.xml
│ │ │ └── metarhia_logo.png
│ │ │ ├── layout
│ │ │ ├── activity_chat.xml
│ │ │ ├── activity_connection.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── bottom_notice.xml
│ │ │ ├── fragment_chat.xml
│ │ │ ├── fragment_chat_login.xml
│ │ │ ├── fragment_connection.xml
│ │ │ ├── fragment_download_code_dialog.xml
│ │ │ ├── fragment_files.xml
│ │ │ ├── fragment_main.xml
│ │ │ ├── fragment_upload_file_dialog.xml
│ │ │ ├── message_file.xml
│ │ │ ├── message_in.xml
│ │ │ ├── message_info.xml
│ │ │ ├── message_out.xml
│ │ │ └── toolbar.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ │ └── xml
│ │ │ └── paths.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── metarhia
│ │ └── metacom
│ │ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MetaCom/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | ### Android template
4 | # Built application files
5 | *.apk
6 | *.ap_
7 |
8 | # Files for the ART/Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 | out/
18 |
19 | # Gradle files
20 | .gradle/
21 | build/
22 |
23 | # Local configuration file (sdk path, etc)
24 | local.properties
25 |
26 | # Proguard folder generated by Eclipse
27 | proguard/
28 |
29 | # Log Files
30 | *.log
31 |
32 | # Android Studio Navigation editor temp files
33 | .navigation/
34 |
35 | # Android Studio captures folder
36 | captures/
37 |
38 | # IntelliJ
39 | *.iml
40 | .idea/workspace.xml
41 | .idea/tasks.xml
42 | .idea/gradle.xml
43 | .idea/dictionaries
44 | .idea/libraries
45 |
46 | # Keystore files
47 | *.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
59 | ### JetBrains template
60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
61 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
62 |
63 | # User-specific stuff:
64 | .idea/**/workspace.xml
65 | .idea/**/tasks.xml
66 |
67 | # Sensitive or high-churn files:
68 | .idea/**/dataSources/
69 | .idea/**/dataSources.ids
70 | .idea/**/dataSources.xml
71 | .idea/**/dataSources.local.xml
72 | .idea/**/sqlDataSources.xml
73 | .idea/**/dynamic.xml
74 | .idea/**/uiDesigner.xml
75 |
76 | # Gradle:
77 | .idea/**/gradle.xml
78 | .idea/**/libraries
79 |
80 | # CMake
81 | cmake-build-debug/
82 | cmake-build-release/
83 |
84 | # Mongo Explorer plugin:
85 | .idea/**/mongoSettings.xml
86 |
87 | ## File-based project format:
88 | *.iws
89 |
90 | ## Plugin-specific files:
91 |
92 | # IntelliJ
93 |
94 | # mpeltonen/sbt-idea plugin
95 | .idea_modules/
96 |
97 | # JIRA plugin
98 | atlassian-ide-plugin.xml
99 |
100 | # Cursive Clojure plugin
101 | .idea/replstate.xml
102 |
103 | # Crashlytics plugin (for Android Studio and IntelliJ)
104 | com_crashlytics_export_strings.xml
105 | crashlytics.properties
106 | crashlytics-build.properties
107 | fabric.properties
108 | ### Gradle template
109 | .gradle
110 | /build/
111 |
112 | # Ignore Gradle GUI config
113 | gradle-app.setting
114 |
115 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
116 | !gradle-wrapper.jar
117 |
118 | # Cache of project
119 | .gradletasknamecache
120 |
121 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
122 | # gradle/wrapper/gradle-wrapper.properties
123 |
--------------------------------------------------------------------------------
/MetaCom/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/MetaCom/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/MetaCom/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MetaCom/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
16 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MetaCom/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MetaCom/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/MetaCom/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MetaCom/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/MetaCom/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '26.0.2'
6 | defaultConfig {
7 | applicationId "com.metarhia.metacom"
8 | minSdkVersion 16
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | javaCompileOptions {
14 | annotationProcessorOptions {
15 | includeCompileClasspath true
16 | }
17 | }
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(include: ['*.jar'], dir: 'libs')
29 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
30 | exclude group: 'com.android.support', module: 'support-annotations'
31 | })
32 | implementation 'com.android.support:appcompat-v7:25.3.1'
33 |
34 | implementation 'com.metarhia.jstp:jstp:0.8.6'
35 | compileOnly group: 'com.metarhia.jstp', name: 'jstp-compiler', version: '0.2.6'
36 | annotationProcessor group: 'com.metarhia.jstp', name: 'jstp-compiler', version: '0.2.6'
37 |
38 | implementation 'com.android.support.constraint:constraint-layout:1.0.1'
39 | implementation 'com.android.support:design:25.3.1'
40 | implementation 'com.jakewharton:butterknife:8.6.0'
41 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
42 | implementation 'com.android.support:support-v4:25.3.1'
43 | implementation 'com.android.support:appcompat-v7:25.3.1'
44 | implementation 'com.android.support:cardview-v7:25.3.1'
45 | implementation 'com.android.support:recyclerview-v7:25.3.1'
46 | testImplementation 'junit:junit:4.12'
47 | }
48 |
--------------------------------------------------------------------------------
/MetaCom/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Lida/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/MetaCom/app/src/androidTest/java/com/metarhia/metacom/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.metarhia.metacom", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
43 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/assets/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} - logback:
5 |
6 |
7 | [%thread] jstp: %msg
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.metarhia.metacom.R;
7 | import com.metarhia.metacom.interfaces.BackPressedHandler;
8 |
9 | import static com.metarhia.metacom.activities.MainFragment.MAIN_FRAGMENT_TAG;
10 |
11 | /**
12 | * @author MariaKokshaikina
13 | */
14 | public class MainActivity extends AppCompatActivity {
15 |
16 | public static final String EXTRA_CONNECTION_ID = "extraConnectionId";
17 | public static final String EXTRA_HOST_NAME = "extraHostName";
18 | public static final String EXTRA_PORT = "extraPort";
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_main);
24 |
25 | int connectionID = getIntent().getIntExtra(EXTRA_CONNECTION_ID, -1);
26 | String hostName = getIntent().getStringExtra(EXTRA_HOST_NAME);
27 | int port = getIntent().getIntExtra(EXTRA_PORT, 0);
28 |
29 | if (savedInstanceState == null) {
30 | if (connectionID != -1) {
31 | getSupportFragmentManager().beginTransaction()
32 | .add(R.id.fragment_container, MainFragment.newInstance(connectionID,
33 | hostName, port), MAIN_FRAGMENT_TAG)
34 | .commit();
35 | }
36 | }
37 | }
38 |
39 | @Override
40 | public void onBackPressed() {
41 | ((BackPressedHandler) getSupportFragmentManager().findFragmentByTag(MAIN_FRAGMENT_TAG))
42 | .handleBackPress();
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/MainFragment.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities;
2 |
3 |
4 | import android.content.DialogInterface;
5 | import android.content.SharedPreferences;
6 | import android.content.pm.PackageManager;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.os.Bundle;
9 | import android.preference.PreferenceManager;
10 | import android.support.annotation.NonNull;
11 | import android.support.design.widget.TabLayout;
12 | import android.support.v4.app.Fragment;
13 | import android.support.v4.app.FragmentPagerAdapter;
14 | import android.support.v4.view.PagerAdapter;
15 | import android.support.v4.view.ViewPager;
16 | import android.support.v7.app.AlertDialog;
17 | import android.view.LayoutInflater;
18 | import android.view.View;
19 | import android.view.ViewGroup;
20 | import android.widget.TextView;
21 | import android.widget.Toast;
22 |
23 | import com.metarhia.metacom.R;
24 | import com.metarhia.metacom.activities.chat.ChatLoginFragment;
25 | import com.metarhia.metacom.activities.files.FilesFragment;
26 | import com.metarhia.metacom.interfaces.BackPressedHandler;
27 | import com.metarhia.metacom.models.UserConnectionsManager;
28 | import com.metarhia.metacom.utils.KeyboardUtils;
29 | import com.metarhia.metacom.utils.PermissionUtils;
30 |
31 | import java.util.ArrayList;
32 |
33 | import butterknife.BindView;
34 | import butterknife.ButterKnife;
35 | import butterknife.OnClick;
36 | import butterknife.Unbinder;
37 |
38 |
39 | /**
40 | * @author MariaKokshaikina
41 | */
42 | public class MainFragment extends Fragment implements BackPressedHandler {
43 |
44 | public final static String MAIN_FRAGMENT_TAG = "MainFragmentTag";
45 | private static final String KEY_CONNECTION_ID = "keyConnectionId";
46 | private static final String KEY_HOST_NAME = "keyHostName";
47 | private static final String KEY_PORT = "keyPort";
48 | private static final String KEY_EXIT_DIALOG = "keyExitDialog";
49 | private static final String KEY_PERMISSIONS_DIALOG = "keyPermissionsDialog";
50 |
51 | @BindView(R.id.toolbar_title)
52 | TextView mToolbarTitle;
53 | @BindView(R.id.tab_layout)
54 | TabLayout mTabLayout;
55 | @BindView(R.id.view_pager)
56 | ViewPager mViewPager;
57 | private Unbinder mUnbinder;
58 |
59 | private ArrayList mFragmentArrayList;
60 | private ArrayList mFragmentTitles;
61 | private int mConnectionID;
62 | private boolean mExitDialog;
63 | private boolean mPermissionsDialog;
64 | private boolean isUIVisible = true;
65 |
66 | public static MainFragment newInstance(int connectionID, String hostName, int port) {
67 | Bundle args = new Bundle();
68 | args.putInt(KEY_CONNECTION_ID, connectionID);
69 | args.putString(KEY_HOST_NAME, hostName);
70 | args.putInt(KEY_PORT, port);
71 |
72 | MainFragment fragment = new MainFragment();
73 | fragment.setArguments(args);
74 | return fragment;
75 | }
76 |
77 | @Override
78 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
79 | Bundle savedInstanceState) {
80 | View view = inflater.inflate(R.layout.fragment_main, container, false);
81 | mUnbinder = ButterKnife.bind(this, view);
82 |
83 | if (getArguments() != null) {
84 | mConnectionID = getArguments().getInt(KEY_CONNECTION_ID);
85 |
86 | String host = getArguments().getString(KEY_HOST_NAME);
87 | String port = String.valueOf(getArguments().getInt(KEY_PORT));
88 |
89 | String toolbarTitle = String.format(getString(R.string.title_pattern), host, port);
90 | setPages();
91 | mToolbarTitle.setText(toolbarTitle);
92 | }
93 |
94 | if (savedInstanceState == null) {
95 | if (PermissionUtils.checkVersion() &&
96 | !PermissionUtils.checkIfAlreadyHavePermission(getContext())) {
97 | showRequestDialog();
98 | }
99 | } else {
100 | if (savedInstanceState.getBoolean(KEY_EXIT_DIALOG)) {
101 | handleBackPress();
102 | }
103 | if (savedInstanceState.getBoolean(KEY_PERMISSIONS_DIALOG)) {
104 | showRequestDialog();
105 | }
106 | }
107 |
108 | return view;
109 | }
110 |
111 | private void showRequestDialog() {
112 | mPermissionsDialog = true;
113 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
114 | builder.setTitle(R.string.permissions)
115 | .setMessage(R.string.permissions_info)
116 | .setCancelable(false)
117 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
118 | @Override
119 | public void onClick(DialogInterface dialogInterface, int i) {
120 | PermissionUtils.requestForStoragePermission(MainFragment.this);
121 | mPermissionsDialog = false;
122 | }
123 | });
124 | AlertDialog alert = builder.create();
125 | alert.show();
126 | }
127 |
128 | @Override
129 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
130 | @NonNull int[] grantResults) {
131 | switch (requestCode) {
132 | case PermissionUtils.REQUEST_CODE: {
133 | if (!(grantResults.length > 0
134 | && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
135 | showForbidDialog();
136 | }
137 | }
138 | }
139 | }
140 |
141 | private void showForbidDialog() {
142 | if (isUIVisible) {
143 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted),
144 | Toast.LENGTH_SHORT).show();
145 | }
146 | }
147 |
148 | @OnClick(R.id.toolbar_back)
149 | public void onBackClick() {
150 | handleBackPress();
151 | }
152 |
153 | public void leaveServer() {
154 | UserConnectionsManager.get().removeConnection(UserConnectionsManager.get().getConnection
155 | (mConnectionID));
156 | }
157 |
158 | private void setPages() {
159 | mFragmentArrayList = new ArrayList<>();
160 | mFragmentArrayList.add(FilesFragment.newInstance(mConnectionID));
161 | mFragmentArrayList.add(ChatLoginFragment.newInstance(mConnectionID));
162 |
163 | PagerAdapter pagerAdapter = new FragmentPagerAdapter(getFragmentManager()) {
164 |
165 | @Override
166 | public int getCount() {
167 | return mFragmentArrayList.size();
168 | }
169 |
170 | @Override
171 | public Fragment getItem(int position) {
172 | return mFragmentArrayList.get(position);
173 | }
174 |
175 | @Override
176 | public CharSequence getPageTitle(int position) {
177 | return mFragmentTitles.get(position);
178 | }
179 | };
180 |
181 | mViewPager.setAdapter(pagerAdapter);
182 |
183 | mFragmentTitles = new ArrayList<>();
184 | mFragmentTitles.add(getResources().getString(R.string.files));
185 | mFragmentTitles.add(getResources().getString(R.string.chat));
186 |
187 | mTabLayout.setupWithViewPager(mViewPager);
188 |
189 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
190 | @Override
191 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
192 |
193 | }
194 |
195 | @Override
196 | public void onPageSelected(int position) {
197 | KeyboardUtils.hideKeyboard(getActivity());
198 | }
199 |
200 | @Override
201 | public void onPageScrollStateChanged(int state) {
202 |
203 | }
204 | });
205 | }
206 |
207 | @Override
208 | public void onDestroyView() {
209 | super.onDestroyView();
210 | mUnbinder.unbind();
211 | }
212 |
213 | @Override
214 | public void handleBackPress() {
215 | mExitDialog = true;
216 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style
217 | .AlertDialogStyle);
218 | builder.setTitle(R.string.leave_server)
219 | .setMessage(R.string.leave_server_desc)
220 | .setCancelable(false)
221 | .setNegativeButton(R.string.cancel,
222 | new DialogInterface.OnClickListener() {
223 | public void onClick(DialogInterface dialog, int id) {
224 | mExitDialog = false;
225 | dialog.cancel();
226 | }
227 | })
228 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
229 | @Override
230 | public void onClick(DialogInterface dialogInterface, int i) {
231 | leaveServer();
232 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences
233 | (getActivity().getApplicationContext());
234 | SharedPreferences.Editor editor = sharedPref.edit();
235 | editor.putBoolean(getString(R.string.shared_preferences_is_authorized), false);
236 | editor.apply();
237 | getActivity().finish();
238 | }
239 | });
240 | AlertDialog alert = builder.create();
241 | alert.show();
242 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color
243 | .black14)));
244 | }
245 |
246 | @Override
247 | public void onSaveInstanceState(Bundle outState) {
248 | super.onSaveInstanceState(outState);
249 | outState.putBoolean(KEY_EXIT_DIALOG, mExitDialog);
250 | outState.putBoolean(KEY_PERMISSIONS_DIALOG, mPermissionsDialog);
251 | }
252 |
253 | @Override
254 | public void onPause() {
255 | super.onPause();
256 | isUIVisible = false;
257 | }
258 |
259 | @Override
260 | public void onResume() {
261 | super.onResume();
262 | isUIVisible = true;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/MetaComApplication.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities;
2 |
3 | import android.app.Application;
4 |
5 | import com.metarhia.metacom.connection.Errors;
6 | import com.metarhia.metacom.utils.Constants;
7 |
8 | /**
9 | * MetaCom application
10 | *
11 | * @author lidaamber
12 | */
13 |
14 | public class MetaComApplication extends Application {
15 |
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 |
20 | Errors.initResources(getResources());
21 | Constants.initResources(getResources());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatActivity.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.chat;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.metarhia.metacom.R;
7 | import com.metarhia.metacom.interfaces.BackPressedHandler;
8 |
9 | import static com.metarhia.metacom.activities.chat.ChatFragment.CHAT_FRAGMENT_TAG;
10 |
11 | /**
12 | * @author MariaKokshaikina
13 | */
14 | public class ChatActivity extends AppCompatActivity {
15 |
16 | public static final String EXTRA_CONNECTION_ID = "extraConnectionId";
17 | public static final String EXTRA_CHAT_ROOM_NAME = "extraChatRoomName";
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_chat);
23 |
24 | int connectionID = getIntent().getIntExtra(EXTRA_CONNECTION_ID, -1);
25 | String chatRoomName = getIntent().getStringExtra(EXTRA_CHAT_ROOM_NAME);
26 |
27 | if (savedInstanceState == null) {
28 | if (connectionID != -1) {
29 | getSupportFragmentManager().beginTransaction()
30 | .add(R.id.chat_container, ChatFragment.newInstance(connectionID,
31 | chatRoomName), CHAT_FRAGMENT_TAG)
32 | .commit();
33 | }
34 | }
35 | }
36 |
37 | @Override
38 | public void onBackPressed() {
39 | ((BackPressedHandler) getSupportFragmentManager().findFragmentByTag(CHAT_FRAGMENT_TAG))
40 | .handleBackPress();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatFragment.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.chat;
2 |
3 |
4 | import android.app.Activity;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.graphics.drawable.ColorDrawable;
9 | import android.net.Uri;
10 | import android.os.Bundle;
11 | import android.os.Environment;
12 | import android.provider.MediaStore;
13 | import android.support.annotation.NonNull;
14 | import android.support.design.widget.TextInputEditText;
15 | import android.support.v4.app.Fragment;
16 | import android.support.v4.content.FileProvider;
17 | import android.support.v7.app.AlertDialog;
18 | import android.support.v7.widget.LinearLayoutManager;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.view.ContextMenu;
21 | import android.view.LayoutInflater;
22 | import android.view.MenuItem;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.webkit.MimeTypeMap;
26 | import android.widget.ImageView;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | import com.metarhia.metacom.BuildConfig;
31 | import com.metarhia.metacom.R;
32 | import com.metarhia.metacom.interfaces.BackPressedHandler;
33 | import com.metarhia.metacom.interfaces.ChatReconnectionListener;
34 | import com.metarhia.metacom.interfaces.FileDownloadedListener;
35 | import com.metarhia.metacom.interfaces.FileUploadedCallback;
36 | import com.metarhia.metacom.interfaces.LeaveRoomCallback;
37 | import com.metarhia.metacom.interfaces.MessageListener;
38 | import com.metarhia.metacom.interfaces.MessageSentCallback;
39 | import com.metarhia.metacom.models.ChatRoom;
40 | import com.metarhia.metacom.models.ChatRoomsManager;
41 | import com.metarhia.metacom.models.Message;
42 | import com.metarhia.metacom.models.UserConnectionsManager;
43 | import com.metarhia.metacom.utils.Constants;
44 | import com.metarhia.metacom.utils.HistoryCallback;
45 | import com.metarhia.metacom.utils.PermissionUtils;
46 |
47 | import java.io.File;
48 | import java.io.FileNotFoundException;
49 | import java.io.InputStream;
50 | import java.util.ArrayList;
51 | import java.util.List;
52 |
53 | import butterknife.BindView;
54 | import butterknife.ButterKnife;
55 | import butterknife.OnClick;
56 | import butterknife.Unbinder;
57 |
58 | import static com.metarhia.metacom.models.MessageType.FILE;
59 | import static com.metarhia.metacom.models.MessageType.INFO;
60 | import static com.metarhia.metacom.models.MessageType.TEXT;
61 | import static com.metarhia.metacom.utils.TextUtils.copyToClipboard;
62 |
63 | /**
64 | * @author MariaKokshaikina
65 | */
66 | public class ChatFragment extends Fragment implements MessageListener, MessageSentCallback,
67 | FileUploadedCallback, LeaveRoomCallback, FileDownloadedListener, BackPressedHandler,
68 | ChatReconnectionListener {
69 |
70 | public static final String CHAT_FRAGMENT_TAG = "ChatFragmentTag";
71 | private static final String KEY_CONNECTION_ID = "keyConnectionId";
72 | private static final String KEY_CHAT_ROOM_NAME = "keyChatRoomName";
73 | private static final String KEY_MESSAGES_LIST = "keyMessagesList";
74 | private static final String KEY_EXIT_DIALOG = "keyExitDialog";
75 |
76 | private static final String TMP_METACOM_JPG = "/tmp-metacom.jpg";
77 | private static final String AUTHORITY_STRING = BuildConfig.APPLICATION_ID + ".provider";
78 | private static final int PICK_IMAGE_FROM_EXPLORER = 0;
79 | private static final int PICK_IMAGE_FROM_CAMERA = 1;
80 | private static final int TAKE_PHOTO = 2;
81 | private static final int FILE_EXPLORER = 3;
82 |
83 | @BindView(R.id.toolbar_title)
84 | TextView mToolbarTitle;
85 | @BindView(R.id.attach)
86 | ImageView mFileAttach;
87 | @BindView(R.id.messages_list)
88 | RecyclerView mMessagesView;
89 | @BindView(R.id.input_message)
90 | TextInputEditText mInputMessage;
91 | private Unbinder mUnbinder;
92 |
93 | private ArrayList mMessages;
94 | private MessagesAdapter mMessagesAdapter;
95 | private ChatRoom mChatRoom;
96 | private ChatRoomsManager mChatRoomsManager;
97 | private boolean isUIVisible = true;
98 | private boolean mExitDialog;
99 |
100 | public static ChatFragment newInstance(int connectionID, String chatRoomName) {
101 | Bundle args = new Bundle();
102 | args.putInt(KEY_CONNECTION_ID, connectionID);
103 | args.putString(KEY_CHAT_ROOM_NAME, chatRoomName);
104 |
105 | ChatFragment fragment = new ChatFragment();
106 | fragment.setArguments(args);
107 | return fragment;
108 | }
109 |
110 | @Override
111 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
112 | Bundle savedInstanceState) {
113 | View v = inflater.inflate(R.layout.fragment_chat, container, false);
114 | mUnbinder = ButterKnife.bind(this, v);
115 |
116 | registerForContextMenu(mFileAttach);
117 |
118 | if (getArguments() != null) {
119 |
120 | int connectionID = getArguments().getInt(KEY_CONNECTION_ID);
121 | String chatRoomName = getArguments().getString(KEY_CHAT_ROOM_NAME);
122 |
123 | mChatRoom = UserConnectionsManager.get().getConnection(connectionID)
124 | .getChatRoomsManager().getChatRoom(chatRoomName);
125 | mChatRoom.setMessageListener(this);
126 | mChatRoom.setChatReconnectionListener(this);
127 |
128 | mChatRoom.setFileDownloadedListener(this);
129 | mChatRoomsManager = UserConnectionsManager.get().getConnection(connectionID)
130 | .getChatRoomsManager();
131 |
132 | mToolbarTitle.setText(chatRoomName);
133 | }
134 |
135 | LinearLayoutManager llm = new LinearLayoutManager(getContext());
136 | mMessagesView.setLayoutManager(llm);
137 |
138 | if (savedInstanceState != null) {
139 | mMessages = (ArrayList) savedInstanceState.getSerializable(KEY_MESSAGES_LIST);
140 | if (savedInstanceState.getBoolean(KEY_EXIT_DIALOG)) {
141 | handleBackPress();
142 | }
143 | } else {
144 | mMessages = new ArrayList<>();
145 | String hasInterlocutorMessage = getString(mChatRoom.hasInterlocutor()
146 | ? R.string
147 | .has_interlocutor : R.string.no_interlocutor);
148 | mMessages.add(new Message(INFO, hasInterlocutorMessage, true));
149 | }
150 |
151 | mMessagesAdapter = new MessagesAdapter(mMessages);
152 | mMessagesView.setAdapter(mMessagesAdapter);
153 |
154 | // still doesn't scroll properly for few last messages in recycleview
155 | mMessagesView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
156 | @Override
157 | public void onLayoutChange(View view, int left, int top, int right, final int bottom,
158 | int oldLeft, int oldTop, int oldRight, final int oldBottom) {
159 | if (bottom < oldBottom) {
160 | mMessagesView.smoothScrollBy(0, oldBottom - bottom);
161 | } else {
162 | int direction = oldBottom - bottom;
163 | if (mMessagesView.canScrollVertically(-direction)) {
164 | mMessagesView.smoothScrollBy(0, direction);
165 | }
166 | }
167 | }
168 | });
169 |
170 | return v;
171 | }
172 |
173 | @Override
174 | public void onSaveInstanceState(Bundle outState) {
175 | super.onSaveInstanceState(outState);
176 | outState.putSerializable(KEY_MESSAGES_LIST, mMessages);
177 | outState.putBoolean(KEY_EXIT_DIALOG, mExitDialog);
178 | }
179 |
180 | @Override
181 | public void onMessageReceived(final Message message) {
182 | displayNewMessage(message);
183 | }
184 |
185 | private void displayNewMessage(Message message) {
186 | mMessages.add(message);
187 | if (isUIVisible) {
188 | updateMessagesView();
189 | }
190 | }
191 |
192 | private void updateMessagesView() {
193 | mMessagesAdapter.notifyDataSetChanged();
194 | mMessagesView.smoothScrollToPosition(mMessages.size());
195 | }
196 |
197 | @Override
198 | public void onMessageSent(final Message message) {
199 | removeErrorIcon(message);
200 | }
201 |
202 | private void removeErrorIcon(Message message) {
203 | mMessages.get(mMessages.indexOf(message)).setWaiting(false);
204 | if (isUIVisible) {
205 | updateMessagesView();
206 | }
207 | }
208 |
209 | @Override
210 | public void onMessageSentError(final String messageError) {
211 | // displayError(message);
212 | }
213 |
214 | private void displayError(String message) {
215 | if (isUIVisible) {
216 | Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
217 | }
218 | }
219 |
220 | @OnClick(R.id.toolbar_back)
221 | public void onToolbarBackClick() {
222 | handleBackPress();
223 | }
224 |
225 | public void leaveRoom() {
226 | mChatRoom.setMessageListener(null);
227 | mChatRoomsManager.leaveChatRoom(mChatRoom, this);
228 | }
229 |
230 | @OnClick(R.id.attach)
231 | public void onFileAttachClick() {
232 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) {
233 | showFileChooser();
234 | } else {
235 | if (PermissionUtils.checkVersion()) {
236 | PermissionUtils.requestForStoragePermission(this);
237 | }
238 | }
239 | }
240 |
241 | private void showForbidDialog() {
242 | if (isUIVisible) {
243 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted),
244 | Toast.LENGTH_SHORT).show();
245 | }
246 | }
247 |
248 | @OnClick(R.id.send)
249 | public void onSendMessageClick() {
250 | String messageText = mInputMessage.getText().toString();
251 | if (!messageText.isEmpty()) {
252 | Message message = new Message(TEXT, messageText, false);
253 | message.setWaiting(true);
254 |
255 | mChatRoom.sendMessage(message, this);
256 | displayNewMessage(message);
257 |
258 | mInputMessage.setText("");
259 | }
260 | }
261 |
262 | @Override
263 | public void onDestroyView() {
264 | super.onDestroyView();
265 | mUnbinder.unbind();
266 | }
267 |
268 | private void showFileChooser() {
269 | getActivity().openContextMenu(mFileAttach);
270 | }
271 |
272 | @Override
273 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo
274 | menuInfo) {
275 | if (v.getId() == mFileAttach.getId()) {
276 | menu.add(0, TAKE_PHOTO, 0, R.string.take_photo);
277 | menu.add(0, FILE_EXPLORER, 0, R.string.file_explorer);
278 | }
279 | }
280 |
281 | @Override
282 | public boolean onContextItemSelected(MenuItem item) {
283 | switch (item.getItemId()) {
284 | case TAKE_PHOTO:
285 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
286 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG);
287 | Uri uri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f);
288 | takePictureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, uri);
289 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
290 | if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
291 | startActivityForResult(takePictureIntent, PICK_IMAGE_FROM_CAMERA);
292 | }
293 | return true;
294 | case FILE_EXPLORER:
295 | Intent intent = new Intent();
296 | intent.setType("*/*");
297 | intent.setAction(Intent.ACTION_GET_CONTENT);
298 | startActivityForResult(Intent.createChooser(intent, getString(R.string
299 | .select_file)),
300 | PICK_IMAGE_FROM_EXPLORER);
301 | return true;
302 | default:
303 | return super.onContextItemSelected(item);
304 | }
305 | }
306 |
307 | @Override
308 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
309 | super.onActivityResult(requestCode, resultCode, data);
310 |
311 | if ((requestCode == PICK_IMAGE_FROM_EXPLORER || requestCode == PICK_IMAGE_FROM_CAMERA) &&
312 | resultCode == Activity.RESULT_OK) {
313 | Uri fileUri = null;
314 | switch (requestCode) {
315 | case PICK_IMAGE_FROM_EXPLORER: {
316 | fileUri = data.getData();
317 | break;
318 | }
319 | case PICK_IMAGE_FROM_CAMERA: {
320 | File f = new File(Environment.getExternalStorageDirectory()
321 | + TMP_METACOM_JPG);
322 | fileUri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f);
323 | break;
324 | }
325 | }
326 | try {
327 | InputStream is = getActivity().getContentResolver().openInputStream(fileUri);
328 | String mimeType = getActivity().getContentResolver().getType(fileUri);
329 | mChatRoom.uploadFile(is, mimeType, this);
330 | } catch (FileNotFoundException e) {
331 | e.printStackTrace();
332 | }
333 | }
334 | }
335 |
336 | @Override
337 | public void onFileUploaded(String fileCode) {
338 | final Message message = new Message(TEXT,
339 | getResources().getString(R.string.uploaded_file), false);
340 | onMessageReceived(message);
341 | }
342 |
343 | @Override
344 | public void onFileUploadError(final String message) {
345 | if (isUIVisible) {
346 | Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
347 | }
348 | }
349 |
350 | @Override
351 | public void onLeavedRoom() {
352 | getActivity().finish();
353 | }
354 |
355 | @Override
356 | public void onLeaveError(final String errorMessage) {
357 | if (isUIVisible) {
358 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
359 | }
360 | }
361 |
362 | @Override
363 | public void onFileDownloaded(String path) {
364 | Message message = new Message(FILE, Constants.composeFilePathInfo(path), true);
365 | displayNewMessage(message);
366 | }
367 |
368 | @Override
369 | public void onFileDownloadError() {
370 | if (isUIVisible) {
371 | Toast.makeText(getContext(), R.string.err_download_failed,
372 | Toast.LENGTH_SHORT).show();
373 | }
374 | }
375 |
376 | private void openFile(String filePath) {
377 | Uri uri = FileProvider.getUriForFile(getActivity(),
378 | BuildConfig.APPLICATION_ID + ".provider",
379 | new File(filePath));
380 |
381 | String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
382 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension
383 | (fileExtension);
384 |
385 | Intent intent = new Intent(Intent.ACTION_VIEW);
386 | intent.setDataAndType(uri, mimeType);
387 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
388 | startActivity(Intent.createChooser(intent, getString(R.string.open_file)));
389 | }
390 |
391 | @Override
392 | public void onPause() {
393 | super.onPause();
394 | isUIVisible = false;
395 | }
396 |
397 | @Override
398 | public void onResume() {
399 | super.onResume();
400 | isUIVisible = true;
401 | updateMessagesView();
402 | }
403 |
404 | @Override
405 | public void handleBackPress() {
406 | mExitDialog = true;
407 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style
408 | .AlertDialogStyle);
409 | builder.setTitle(R.string.leave_chat)
410 | .setMessage(R.string.leave_chat_desc)
411 | .setCancelable(false)
412 | .setNegativeButton(R.string.cancel,
413 | new DialogInterface.OnClickListener() {
414 | public void onClick(DialogInterface dialog, int id) {
415 | mExitDialog = false;
416 | dialog.cancel();
417 | }
418 | })
419 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
420 | @Override
421 | public void onClick(DialogInterface dialogInterface, int i) {
422 | showSaveHistoryDialog();
423 | }
424 | });
425 | AlertDialog alert = builder.create();
426 | alert.show();
427 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color
428 | .black14)));
429 | }
430 |
431 | private void showSaveHistoryDialog() {
432 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style
433 | .AlertDialogStyle);
434 | builder.setTitle(R.string.save_history)
435 | .setMessage(R.string.save_history_desc)
436 | .setCancelable(false)
437 | .setNegativeButton(R.string.no,
438 | new DialogInterface.OnClickListener() {
439 | public void onClick(DialogInterface dialog, int id) {
440 | leaveRoom();
441 | }
442 | })
443 | .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
444 | @Override
445 | public void onClick(DialogInterface dialogInterface, int i) {
446 | saveHistory();
447 | leaveRoom();
448 | }
449 | });
450 | AlertDialog alert = builder.create();
451 | alert.show();
452 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color
453 | .black14)));
454 | }
455 |
456 | private void saveHistory() {
457 | mChatRoomsManager.saveHistory(mMessages, new HistoryCallback() {
458 | @Override
459 | public void onHistorySaved(String filename) {
460 | String message = String.format(getString(R.string.saved_history), filename);
461 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT)
462 | .show();
463 | }
464 |
465 | @Override
466 | public void onSaveError() {
467 | Toast.makeText(getActivity(),
468 | getString(R.string.save_history_error), Toast.LENGTH_SHORT)
469 | .show();
470 | }
471 | });
472 | }
473 |
474 | @Override
475 | public void onConnectionLost() {
476 | if (isUIVisible) {
477 | Toast.makeText(getContext(), getString(R.string.connection_lost), Toast.LENGTH_SHORT)
478 | .show();
479 | }
480 | }
481 |
482 | @Override
483 | public void onRejoinSuccess(boolean hasInterlocutor) {
484 | if (isUIVisible) {
485 | Toast.makeText(getContext(), getString(R.string.connection_established), Toast
486 | .LENGTH_SHORT).show();
487 | }
488 | }
489 |
490 | @Override
491 | public void onRejoinError(String errorMessage) {
492 | if (isUIVisible) {
493 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
494 | }
495 | }
496 |
497 | @Override
498 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
499 | @NonNull int[] grantResults) {
500 | switch (requestCode) {
501 | case PermissionUtils.REQUEST_CODE: {
502 | if (grantResults.length > 0
503 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
504 | showFileChooser();
505 | } else {
506 | showForbidDialog();
507 | }
508 | }
509 | }
510 | }
511 |
512 | class MessagesAdapter extends RecyclerView.Adapter {
513 |
514 | private static final int TYPE_INFO = 0;
515 | private static final int TYPE_INCOMING_FILE = 1;
516 | private static final int TYPE_TEXT_IN = 2;
517 | private static final int TYPE_TEXT_OUT = 3;
518 |
519 | private List messages;
520 |
521 | MessagesAdapter(List messages) {
522 | this.messages = messages;
523 | }
524 |
525 | @Override
526 | public int getItemViewType(int position) {
527 | Message message = messages.get(position);
528 | if (message.getType() == INFO) return TYPE_INFO;
529 | if (message.getType() == FILE && message.isIncoming()) return TYPE_INCOMING_FILE;
530 | if (message.getType() == TEXT)
531 | return message.isIncoming() ? TYPE_TEXT_IN : TYPE_TEXT_OUT;
532 | return -1;
533 | }
534 |
535 | @Override
536 | public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
537 | int resource = -1;
538 | if (viewType == TYPE_INFO) {
539 | resource = R.layout.message_info;
540 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
541 | return new InfoMessageViewHolder(v);
542 | }
543 | if (viewType == TYPE_INCOMING_FILE) {
544 | resource = R.layout.message_file;
545 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
546 | return new FileMessageViewHolder(v);
547 | }
548 | if (viewType == TYPE_TEXT_IN) resource = R.layout.message_in;
549 | if (viewType == TYPE_TEXT_OUT) resource = R.layout.message_out;
550 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
551 | return new TextMessageViewHolder(v);
552 | }
553 |
554 | @Override
555 | public void onBindViewHolder(MessageViewHolder holder, int position) {
556 | final Message message = messages.get(position);
557 | final String messageContent = message.getContent();
558 | if (holder instanceof TextMessageViewHolder) {
559 | TextMessageViewHolder textMessageViewHolder = (TextMessageViewHolder) holder;
560 | textMessageViewHolder.messageText.setText(messageContent);
561 | textMessageViewHolder.errorIcon.setVisibility(message.isWaiting() ? View
562 | .VISIBLE : View.GONE);
563 | registerForContextMenu(textMessageViewHolder.messageLayout);
564 | textMessageViewHolder.messageLayout.setOnCreateContextMenuListener(new View
565 | .OnCreateContextMenuListener() {
566 | @Override
567 | public void onCreateContextMenu(ContextMenu contextMenu, View view,
568 | ContextMenu.ContextMenuInfo contextMenuInfo) {
569 | contextMenu.add(getString(R.string.copy)).setOnMenuItemClickListener
570 | (new MenuItem.OnMenuItemClickListener() {
571 | @Override
572 | public boolean onMenuItemClick(MenuItem menuItem) {
573 | copyToClipboard(getActivity(), messageContent);
574 | return false;
575 | }
576 | });
577 | if (message.isWaiting()) {
578 | contextMenu.add(getString(R.string.resend))
579 | .setOnMenuItemClickListener(new MenuItem
580 | .OnMenuItemClickListener() {
581 | @Override
582 | public boolean onMenuItemClick(MenuItem menuItem) {
583 | mMessages.remove(message);
584 | mChatRoom.sendMessage(message, ChatFragment.this);
585 | displayNewMessage(message);
586 | return false;
587 | }
588 | });
589 | }
590 | }
591 | });
592 | }
593 | if (holder instanceof FileMessageViewHolder) {
594 | FileMessageViewHolder fileMessageViewHolder = (FileMessageViewHolder) holder;
595 | fileMessageViewHolder.fileImageView.setOnClickListener(new View.OnClickListener() {
596 | @Override
597 | public void onClick(View view) {
598 | String path = messageContent.substring(messageContent.indexOf('/'));
599 | ChatFragment.this.openFile(path);
600 | }
601 | });
602 | }
603 | if (holder instanceof InfoMessageViewHolder) {
604 | InfoMessageViewHolder infoMessageViewHolder = (InfoMessageViewHolder) holder;
605 | infoMessageViewHolder.messageText.setText(messageContent);
606 | }
607 | }
608 |
609 | @Override
610 | public int getItemCount() {
611 | return messages.size();
612 | }
613 |
614 | class MessageViewHolder extends RecyclerView.ViewHolder {
615 |
616 | MessageViewHolder(View itemView) {
617 | super(itemView);
618 | }
619 | }
620 |
621 | class TextMessageViewHolder extends MessageViewHolder {
622 |
623 | private TextView messageText;
624 | private View messageLayout;
625 | private ImageView errorIcon;
626 |
627 | TextMessageViewHolder(View itemView) {
628 | super(itemView);
629 | messageText = ButterKnife.findById(itemView, R.id.message_text);
630 | messageLayout = ButterKnife.findById(itemView, R.id.message_layout);
631 | errorIcon = ButterKnife.findById(itemView, R.id.send_error);
632 | }
633 |
634 | }
635 |
636 | class FileMessageViewHolder extends MessageViewHolder {
637 |
638 | private ImageView fileImageView;
639 |
640 | private FileMessageViewHolder(View itemView) {
641 | super(itemView);
642 | fileImageView = ButterKnife.findById(itemView, R.id.file);
643 | }
644 | }
645 |
646 | class InfoMessageViewHolder extends MessageViewHolder {
647 |
648 | private TextView messageText;
649 |
650 | InfoMessageViewHolder(View itemView) {
651 | super(itemView);
652 | messageText = ButterKnife.findById(itemView, R.id.message_text);
653 | }
654 | }
655 | }
656 | }
657 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatLoginFragment.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.chat;
2 |
3 |
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.design.widget.TextInputEditText;
7 | import android.support.v4.app.Fragment;
8 | import android.support.v7.widget.AppCompatButton;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.ProgressBar;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.metarhia.metacom.R;
17 | import com.metarhia.metacom.interfaces.JoinRoomCallback;
18 | import com.metarhia.metacom.models.ChatRoomsManager;
19 | import com.metarhia.metacom.models.UserConnectionsManager;
20 |
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 | import butterknife.OnClick;
24 | import butterknife.Unbinder;
25 |
26 |
27 | /**
28 | * @author MariaKokshaikina
29 | */
30 | public class ChatLoginFragment extends Fragment implements JoinRoomCallback {
31 |
32 | private static final String KEY_CONNECTION_ID = "keyConnectionId";
33 |
34 | @BindView(R.id.chat_name)
35 | TextInputEditText mChatNameEditText;
36 | @BindView(R.id.submit)
37 | TextView mButtonSubmit;
38 | @BindView(R.id.spinner)
39 | ProgressBar mSpinner;
40 | private Unbinder mUnbinder;
41 |
42 | private ChatRoomsManager mManager;
43 | private int mID;
44 | private boolean isUIVisible = true;
45 |
46 | public static ChatLoginFragment newInstance(int connectionID) {
47 | Bundle args = new Bundle();
48 | args.putInt(KEY_CONNECTION_ID, connectionID);
49 | ChatLoginFragment fragment = new ChatLoginFragment();
50 | fragment.setArguments(args);
51 | return fragment;
52 | }
53 |
54 | @Override
55 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
56 | Bundle savedInstanceState) {
57 | View view = inflater.inflate(R.layout.fragment_chat_login, container, false);
58 | mUnbinder = ButterKnife.bind(this, view);
59 |
60 | setRetainInstance(true);
61 |
62 | if (getArguments() != null && getArguments().containsKey(KEY_CONNECTION_ID)) {
63 | mID = getArguments().getInt(KEY_CONNECTION_ID);
64 | mManager = UserConnectionsManager.get().getConnection(mID).getChatRoomsManager();
65 | }
66 |
67 | return view;
68 | }
69 |
70 | @OnClick(R.id.submit)
71 | public void onButtonSubmitClick() {
72 | String chatName = mChatNameEditText.getText().toString();
73 | if (!chatName.isEmpty()) {
74 | mButtonSubmit.setVisibility(View.GONE);
75 | mSpinner.setVisibility(View.VISIBLE);
76 | mChatNameEditText.setEnabled(false);
77 | mManager.addChatRoom(chatName, this);
78 | }
79 | }
80 |
81 | @Override
82 | public void onJoinedRoom(boolean hasInterlocutor) {
83 | mButtonSubmit.setVisibility(View.VISIBLE);
84 | mSpinner.setVisibility(View.INVISIBLE);
85 | mChatNameEditText.setEnabled(true);
86 | Intent intent = new Intent(getActivity(), ChatActivity.class);
87 | intent.putExtra(ChatActivity.EXTRA_CONNECTION_ID, mID);
88 | intent.putExtra(ChatActivity.EXTRA_CHAT_ROOM_NAME, mChatNameEditText.getText().toString());
89 | startActivity(intent);
90 | }
91 |
92 | @Override
93 | public void onJoinError(final String errorMessage) {
94 | mButtonSubmit.setVisibility(View.VISIBLE);
95 | mSpinner.setVisibility(View.INVISIBLE);
96 | mChatNameEditText.setEnabled(true);
97 | if (isUIVisible) {
98 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
99 | }
100 | }
101 |
102 | @Override
103 | public void onDestroyView() {
104 | super.onDestroyView();
105 | mUnbinder.unbind();
106 | }
107 |
108 | @Override
109 | public void onResume() {
110 | super.onResume();
111 | isUIVisible = true;
112 | }
113 |
114 | @Override
115 | public void onPause() {
116 | super.onPause();
117 | isUIVisible = false;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/connection/ConnectionActivity.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.connection;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.metarhia.metacom.R;
7 |
8 | /**
9 | * @author MariaKokshaikina
10 | */
11 | public class ConnectionActivity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_connection);
17 |
18 | if (savedInstanceState == null) {
19 | getSupportFragmentManager().beginTransaction()
20 | .add(R.id.fragment_container, new ConnectionFragment())
21 | .commit();
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/connection/ConnectionFragment.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.connection;
2 |
3 |
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.net.Uri;
8 | import android.os.Bundle;
9 | import android.preference.PreferenceManager;
10 | import android.support.design.widget.TextInputEditText;
11 | import android.support.v4.app.Fragment;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.AdapterView;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.AutoCompleteTextView;
18 | import android.widget.ProgressBar;
19 | import android.widget.TextView;
20 | import android.widget.Toast;
21 |
22 | import com.metarhia.metacom.R;
23 | import com.metarhia.metacom.activities.MainActivity;
24 | import com.metarhia.metacom.interfaces.ConnectionCallback;
25 | import com.metarhia.metacom.models.ConnectionInfoProvider;
26 | import com.metarhia.metacom.models.UserConnectionsManager;
27 |
28 | import java.util.Map;
29 |
30 | import butterknife.BindView;
31 | import butterknife.ButterKnife;
32 | import butterknife.OnClick;
33 | import butterknife.Unbinder;
34 |
35 | /**
36 | * @author MariaKokshaikina
37 | */
38 | public class ConnectionFragment extends Fragment implements ConnectionCallback {
39 |
40 | @BindView(R.id.host)
41 | AutoCompleteTextView mHostEditText;
42 | @BindView(R.id.port)
43 | TextInputEditText mPortEditText;
44 | @BindView(R.id.submit)
45 | TextView mButtonSubmit;
46 | @BindView(R.id.spinner)
47 | ProgressBar mSpinner;
48 | private Unbinder mUnbinder;
49 |
50 | private String mHost;
51 | private Integer mPort;
52 | private boolean mIsUIVisible = true;
53 |
54 | @Override
55 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
56 | Bundle savedInstanceState) {
57 | View v = inflater.inflate(R.layout.fragment_connection, container, false);
58 | mUnbinder = ButterKnife.bind(this, v);
59 |
60 | final Map infoList = ConnectionInfoProvider.restoreConnectionInfo
61 | (getActivity());
62 | if (infoList != null) {
63 | String[] hosts = infoList.keySet().toArray(new String[infoList.size()]);
64 | final ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout
65 | .support_simple_spinner_dropdown_item, hosts);
66 | mHostEditText.setAdapter(adapter);
67 | mHostEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
68 | @Override
69 | public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
70 | String host = adapter.getItem(i);
71 | mPortEditText.setText(infoList.get(host) + "");
72 | }
73 | });
74 | }
75 |
76 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity
77 | ().getApplicationContext());
78 | String host = sharedPref.getString(getString(R.string.shared_preferences_host), null);
79 | int port = sharedPref.getInt(getString(R.string.shared_preferences_port), -1);
80 | boolean isAuthorized = sharedPref.getBoolean(getString(R.string
81 | .shared_preferences_is_authorized), false);
82 | if (host != null && port != -1) {
83 | mHostEditText.setText(host);
84 | mPortEditText.setText(port + "");
85 | }
86 | if (isAuthorized) {
87 | setButtonSubmitClick();
88 | }
89 |
90 | return v;
91 | }
92 |
93 | @OnClick(R.id.submit)
94 | public void setButtonSubmitClick() {
95 | mHost = mHostEditText.getText().toString();
96 | if (!mPortEditText.getText().toString().isEmpty() && !mHost.isEmpty()) {
97 | mPort = Integer.valueOf(mPortEditText.getText().toString());
98 | ConnectionInfoProvider.saveConnectionInfo(getActivity(), mHost, mPort);
99 | mButtonSubmit.setVisibility(View.INVISIBLE);
100 | mSpinner.setVisibility(View.VISIBLE);
101 | mHostEditText.setEnabled(false);
102 | mPortEditText.setEnabled(false);
103 | Context context = getActivity().getApplicationContext();
104 | UserConnectionsManager.get().addConnection(context, mHost, mPort, this);
105 | }
106 | }
107 |
108 | @OnClick(R.id.installation_guide)
109 | public void showInstallationGuide() {
110 | Intent i = new Intent(Intent.ACTION_VIEW);
111 | i.setData(Uri.parse(getString(R.string.installation_guide_link)));
112 | startActivity(Intent.createChooser(i, getString(R.string.installation_chooser)));
113 | }
114 |
115 | @Override
116 | public void onConnectionEstablished(final int connectionID) {
117 | if (mIsUIVisible) {
118 | mButtonSubmit.setVisibility(View.VISIBLE);
119 | mSpinner.setVisibility(View.INVISIBLE);
120 | mHostEditText.setEnabled(true);
121 | mPortEditText.setEnabled(true);
122 | }
123 |
124 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity
125 | ().getApplicationContext());
126 | SharedPreferences.Editor editor = sharedPref.edit();
127 | editor.putString(getString(R.string.shared_preferences_host), mHost);
128 | editor.putInt(getString(R.string.shared_preferences_port), mPort);
129 | editor.putBoolean(getString(R.string.shared_preferences_is_authorized), true);
130 | editor.apply();
131 |
132 | Intent intent = new Intent(getActivity(), MainActivity.class);
133 | intent.putExtra(MainActivity.EXTRA_CONNECTION_ID, connectionID);
134 | intent.putExtra(MainActivity.EXTRA_HOST_NAME, mHost);
135 | intent.putExtra(MainActivity.EXTRA_PORT, mPort);
136 | startActivity(intent);
137 | }
138 |
139 | @Override
140 | public void onConnectionError() {
141 | if (mIsUIVisible) {
142 | mButtonSubmit.setVisibility(View.VISIBLE);
143 | mSpinner.setVisibility(View.INVISIBLE);
144 | mHostEditText.setEnabled(true);
145 | mPortEditText.setEnabled(true);
146 | Toast.makeText(getContext(), getString(R.string.connection_error), Toast
147 | .LENGTH_SHORT).show();
148 | }
149 | }
150 |
151 | @Override
152 | public void onDestroyView() {
153 | super.onDestroyView();
154 | mUnbinder.unbind();
155 | }
156 |
157 | @Override
158 | public void onPause() {
159 | super.onPause();
160 | mIsUIVisible = false;
161 | }
162 |
163 | @Override
164 | public void onResume() {
165 | super.onResume();
166 | mIsUIVisible = true;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/DownloadFileDialog.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.files;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.graphics.drawable.ColorDrawable;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.design.widget.TextInputEditText;
10 | import android.support.v4.app.DialogFragment;
11 | import android.support.v7.app.AlertDialog;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 |
15 | import com.metarhia.metacom.R;
16 | import com.metarhia.metacom.interfaces.DownloadFileByCodeListener;
17 |
18 | import butterknife.ButterKnife;
19 |
20 |
21 | /**
22 | * @author MariaKokshaikina
23 | */
24 | public class DownloadFileDialog extends DialogFragment {
25 |
26 | public final static String DownloadFileDialogTag = "DownloadFileDialogTag";
27 |
28 | private DownloadFileByCodeListener mListener = null;
29 |
30 | @NonNull
31 | @Override
32 | public Dialog onCreateDialog(Bundle savedInstanceState) {
33 |
34 | LayoutInflater layoutInflater = getActivity().getLayoutInflater();
35 |
36 | final View view = layoutInflater.inflate(R.layout.fragment_download_code_dialog, null);
37 |
38 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
39 | .setView(view)
40 | .setPositiveButton(getResources().getString(R.string.download),
41 | new DialogInterface.OnClickListener() {
42 | public void onClick(DialogInterface dialog, int whichButton) {
43 | TextInputEditText code = ButterKnife.findById(view, R.id.file_code);
44 | String fileCode = code.getText().toString();
45 | mListener.downloadByCode(fileCode);
46 | }
47 | }
48 | )
49 | .setNegativeButton(getResources().getString(R.string.cancel),
50 | new DialogInterface.OnClickListener() {
51 | public void onClick(DialogInterface dialog, int whichButton) {
52 | dialog.dismiss();
53 | }
54 | }
55 | );
56 |
57 | Dialog dialog = builder.create();
58 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R
59 | .color.black14)));
60 | return dialog;
61 | }
62 |
63 | public void setDownloadFileByCodeListener(DownloadFileByCodeListener listener) {
64 | mListener = listener;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/FilesFragment.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.files;
2 |
3 |
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.net.Uri;
8 | import android.os.Bundle;
9 | import android.os.Environment;
10 | import android.provider.MediaStore;
11 | import android.support.annotation.NonNull;
12 | import android.support.v4.app.DialogFragment;
13 | import android.support.v4.app.Fragment;
14 | import android.support.v4.content.FileProvider;
15 | import android.support.v4.content.res.ResourcesCompat;
16 | import android.view.ContextMenu;
17 | import android.view.LayoutInflater;
18 | import android.view.MenuItem;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.webkit.MimeTypeMap;
22 | import android.widget.ImageView;
23 | import android.widget.TextView;
24 | import android.widget.Toast;
25 |
26 | import com.metarhia.metacom.BuildConfig;
27 | import com.metarhia.metacom.R;
28 | import com.metarhia.metacom.interfaces.DownloadFileByCodeListener;
29 | import com.metarhia.metacom.interfaces.FileDownloadedListener;
30 | import com.metarhia.metacom.interfaces.FileUploadedCallback;
31 | import com.metarhia.metacom.models.FilesManager;
32 | import com.metarhia.metacom.models.UserConnectionsManager;
33 | import com.metarhia.metacom.utils.PermissionUtils;
34 |
35 | import java.io.File;
36 | import java.io.FileNotFoundException;
37 | import java.io.InputStream;
38 |
39 | import butterknife.BindView;
40 | import butterknife.ButterKnife;
41 | import butterknife.OnClick;
42 | import butterknife.Unbinder;
43 |
44 | import static com.metarhia.metacom.activities.files.DownloadFileDialog.DownloadFileDialogTag;
45 | import static com.metarhia.metacom.activities.files.UploadFileDialog.UploadFileDialogTag;
46 |
47 |
48 | /**
49 | * @author MariaKokshaikina
50 | */
51 | public class FilesFragment extends Fragment implements FileDownloadedListener,
52 | FileUploadedCallback, DownloadFileByCodeListener {
53 |
54 | private static final String KEY_CONNECTION_ID = "keyConnectionId";
55 | private static final String KEY_BOTTOM_NOTICE = "keyBottomNotice";
56 | private static final String KEY_BOTTOM_MESSAGE = "keyBottomMessage";
57 | private static final String KEY_OPEN_FILE = "keyOpenFile";
58 | private static final String KEY_FILE_URI = "keyFileUri";
59 | private static final String TMP_METACOM_JPG = "/tmp-metacom.jpg";
60 | private static final String AUTHORITY_STRING = BuildConfig.APPLICATION_ID + ".provider";
61 |
62 | private static final int PICK_IMAGE_FROM_EXPLORER = 0;
63 | private static final int PICK_IMAGE_FROM_CAMERA = 1;
64 | private static final int TAKE_PHOTO = 2;
65 | private static final int FILE_EXPLORER = 3;
66 |
67 | @BindView(R.id.bottom_notice_text)
68 | TextView mBottomNoticeText;
69 | @BindView(R.id.bottom_notice_layout)
70 | View mBottomNoticeLayout;
71 | @BindView(R.id.upload_file)
72 | ImageView mUploadFile;
73 | @BindView(R.id.download_file)
74 | ImageView mDownloadFile;
75 | private Unbinder mUnbinder;
76 |
77 | private FilesManager mFilesManager;
78 | private boolean mBottomNotice;
79 | private boolean mOpenFile;
80 | private String mFilePath;
81 | private boolean isUIVisible;
82 | private boolean showUploadDialog;
83 | private String showUploadDialogCode;
84 |
85 | public static FilesFragment newInstance(int connectionID) {
86 | Bundle args = new Bundle();
87 | args.putInt(KEY_CONNECTION_ID, connectionID);
88 | FilesFragment fragment = new FilesFragment();
89 | fragment.setArguments(args);
90 | return fragment;
91 | }
92 |
93 | @Override
94 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
95 | Bundle savedInstanceState) {
96 |
97 | View view = inflater.inflate(R.layout.fragment_files, container, false);
98 | mUnbinder = ButterKnife.bind(this, view);
99 |
100 | setRetainInstance(true);
101 |
102 | registerForContextMenu(mUploadFile);
103 |
104 | if (getArguments() != null) {
105 | int connectionID = getArguments().getInt(KEY_CONNECTION_ID);
106 | mFilesManager = UserConnectionsManager.get().getConnection(connectionID)
107 | .getFilesManager();
108 | }
109 |
110 | if (savedInstanceState != null) {
111 | if (savedInstanceState.getBoolean(KEY_BOTTOM_NOTICE)) {
112 | setBottomNoticeMessage(savedInstanceState.getString(KEY_BOTTOM_MESSAGE));
113 | }
114 | if (savedInstanceState.getBoolean(KEY_OPEN_FILE)) {
115 | String fileUri = savedInstanceState.getString(KEY_FILE_URI);
116 | onFileDownloaded(fileUri);
117 | }
118 | }
119 |
120 | return view;
121 | }
122 |
123 | public void hideBottomNotice() {
124 | mBottomNotice = false;
125 | mBottomNoticeLayout.setVisibility(View.GONE);
126 | }
127 |
128 | public void setBottomNoticeMessage(String message) {
129 | mBottomNotice = true;
130 | mBottomNoticeLayout.setVisibility(View.VISIBLE);
131 | mBottomNoticeText.setText(message);
132 | }
133 |
134 | @OnClick(R.id.download_file)
135 | public void onDownloadFileClick() {
136 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) {
137 | DownloadFileDialog dialog = new DownloadFileDialog();
138 | dialog.setDownloadFileByCodeListener(this);
139 | dialog.show(getActivity().getSupportFragmentManager(), DownloadFileDialogTag);
140 | } else {
141 | if (PermissionUtils.checkVersion()) {
142 | PermissionUtils.requestForStoragePermission(this);
143 | }
144 | }
145 | }
146 |
147 | @OnClick(R.id.upload_file)
148 | public void onUploadFileClick() {
149 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) {
150 | showFileChooser();
151 | } else {
152 | if (PermissionUtils.checkVersion()) {
153 | PermissionUtils.requestForStoragePermission(this);
154 | }
155 | }
156 | }
157 |
158 | private void showForbidDialog() {
159 | if (isUIVisible) {
160 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted),
161 | Toast.LENGTH_SHORT).show();
162 | }
163 | }
164 |
165 | @Override
166 | public void onFileDownloaded(final String filePath) {
167 | setBottomNoticeMessage(getString(R.string.complete));
168 | mDownloadFile.setEnabled(true);
169 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable
170 | .ic_file_download_24dp, null));
171 | mOpenFile = true;
172 | mFilePath = filePath;
173 | mBottomNoticeLayout.setOnClickListener(new View.OnClickListener() {
174 | @Override
175 | public void onClick(View view) {
176 | openFile(mFilePath);
177 | mOpenFile = false;
178 | mBottomNoticeLayout.setOnClickListener(null);
179 | hideBottomNotice();
180 | }
181 | });
182 | }
183 |
184 | private void openFile(String filePath) {
185 | Uri uri = FileProvider.getUriForFile(getActivity(),
186 | BuildConfig.APPLICATION_ID + ".provider",
187 | new File(filePath));
188 |
189 | String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
190 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension
191 | (fileExtension);
192 |
193 | Intent intent = new Intent(Intent.ACTION_VIEW);
194 | intent.setDataAndType(uri, mimeType);
195 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
196 | startActivity(Intent.createChooser(intent, getString(R.string.open_file)));
197 | }
198 |
199 | @Override
200 | public void onFileDownloadError() {
201 | if (isUIVisible) {
202 | Toast.makeText(getContext(), getString(R.string.download_failed), Toast.LENGTH_SHORT)
203 | .show();
204 | }
205 | hideBottomNotice();
206 | mDownloadFile.setEnabled(true);
207 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable
208 | .ic_file_download_24dp, null));
209 | }
210 |
211 | @Override
212 | public void onFileUploaded(final String fileCode) {
213 | if (isUIVisible) {
214 | showUploadDialog = false;
215 | hideBottomNotice();
216 | DialogFragment dialog = UploadFileDialog.newInstance(fileCode);
217 | dialog.show(getActivity().getSupportFragmentManager(), UploadFileDialogTag);
218 | } else {
219 | showUploadDialog = true;
220 | showUploadDialogCode = fileCode;
221 | }
222 | mUploadFile.setEnabled(true);
223 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable
224 | .ic_file_upload_24dp, null));
225 | }
226 |
227 | @Override
228 | public void onFileUploadError(final String message) {
229 | if (isUIVisible) {
230 | Toast.makeText(getContext(), getString(R.string.err_upload_failed), Toast
231 | .LENGTH_SHORT).show();
232 | }
233 | hideBottomNotice();
234 | mUploadFile.setEnabled(true);
235 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable
236 | .ic_file_upload_24dp, null));
237 | }
238 |
239 | @Override
240 | public void onDestroyView() {
241 | super.onDestroyView();
242 | mUnbinder.unbind();
243 | }
244 |
245 | @Override
246 | public void downloadByCode(String code) {
247 | setBottomNoticeMessage(getString(R.string.downloading_dots));
248 | mFilesManager.downloadFile(code, this);
249 | mDownloadFile.setEnabled(false);
250 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable
251 | .ic_file_download_grey_24dp, null));
252 | }
253 |
254 | @Override
255 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
256 | super.onActivityResult(requestCode, resultCode, data);
257 |
258 | if ((requestCode == PICK_IMAGE_FROM_EXPLORER || requestCode == PICK_IMAGE_FROM_CAMERA) &&
259 | resultCode == Activity.RESULT_OK) {
260 | Uri fileUri = null;
261 | switch (requestCode) {
262 | case PICK_IMAGE_FROM_EXPLORER: {
263 | fileUri = data.getData();
264 | break;
265 | }
266 | case PICK_IMAGE_FROM_CAMERA: {
267 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG);
268 | fileUri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f);
269 | break;
270 | }
271 | }
272 | try {
273 | InputStream is = getActivity().getContentResolver().openInputStream(fileUri);
274 | mFilesManager.uploadFile(is, this);
275 | setBottomNoticeMessage(getString(R.string.uploading_dots));
276 | mUploadFile.setEnabled(false);
277 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R
278 | .drawable.ic_file_upload_grey_24dp, null));
279 | } catch (FileNotFoundException e) {
280 | e.printStackTrace();
281 | }
282 | }
283 | }
284 |
285 | private void showFileChooser() {
286 | getActivity().openContextMenu(mUploadFile);
287 | }
288 |
289 | @Override
290 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo
291 | menuInfo) {
292 | if (v.getId() == mUploadFile.getId()) {
293 | menu.add(0, TAKE_PHOTO, 0, R.string.take_photo);
294 | menu.add(0, FILE_EXPLORER, 0, R.string.file_explorer);
295 | }
296 | }
297 |
298 | @Override
299 | public boolean onContextItemSelected(MenuItem item) {
300 | switch (item.getItemId()) {
301 | case TAKE_PHOTO:
302 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
303 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG);
304 | Uri uri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f);
305 | takePictureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, uri);
306 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
307 | if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
308 | startActivityForResult(takePictureIntent, PICK_IMAGE_FROM_CAMERA);
309 | }
310 | return true;
311 | case FILE_EXPLORER:
312 | Intent intent = new Intent();
313 | intent.setType("*/*");
314 | intent.setAction(Intent.ACTION_GET_CONTENT);
315 | startActivityForResult(Intent.createChooser(intent, getString(R.string
316 | .select_file)), PICK_IMAGE_FROM_EXPLORER);
317 | return true;
318 | default:
319 | return super.onContextItemSelected(item);
320 | }
321 | }
322 |
323 | @Override
324 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
325 | @NonNull int[] grantResults) {
326 | switch (requestCode) {
327 | case PermissionUtils.REQUEST_CODE: {
328 | if (grantResults.length > 0
329 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
330 | showFileChooser();
331 | } else {
332 | showForbidDialog();
333 | }
334 | }
335 | }
336 | }
337 |
338 | @Override
339 | public void onSaveInstanceState(Bundle outState) {
340 | super.onSaveInstanceState(outState);
341 | if (mBottomNotice) {
342 | outState.putBoolean(KEY_BOTTOM_NOTICE, mBottomNotice);
343 | outState.putString(KEY_BOTTOM_MESSAGE, mBottomNoticeText.getText().toString());
344 | }
345 | if (mOpenFile) {
346 | outState.putBoolean(KEY_OPEN_FILE, mOpenFile);
347 | outState.putString(KEY_FILE_URI, mFilePath);
348 | }
349 | }
350 |
351 | @Override
352 | public void onPause() {
353 | super.onPause();
354 | isUIVisible = false;
355 | }
356 |
357 | @Override
358 | public void onResume() {
359 | super.onResume();
360 | isUIVisible = true;
361 | if (showUploadDialog) {
362 | onFileUploaded(showUploadDialogCode);
363 | }
364 | }
365 | }
366 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/UploadFileDialog.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.activities.files;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.graphics.drawable.ColorDrawable;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.v4.app.DialogFragment;
10 | import android.support.v7.app.AlertDialog;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.metarhia.metacom.R;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 | import butterknife.Unbinder;
21 |
22 | import static com.metarhia.metacom.utils.TextUtils.copyToClipboard;
23 |
24 | /**
25 | * @author MariaKokshaikina
26 | */
27 | public class UploadFileDialog extends DialogFragment {
28 |
29 | public final static String UploadFileDialogTag = "UploadFileDialogTag";
30 | public final static String KEY_UPLOAD_FILE_CODE = "KEY_UPLOAD_FILE_CODE";
31 |
32 | @BindView(R.id.upload_result_string)
33 | TextView mUploadResultString;
34 | private Unbinder mUnbinder;
35 |
36 | private boolean isUIVisible = true;
37 |
38 | public static UploadFileDialog newInstance(String fileCode) {
39 | Bundle args = new Bundle();
40 | args.putString(KEY_UPLOAD_FILE_CODE, fileCode);
41 |
42 | UploadFileDialog fragment = new UploadFileDialog();
43 | fragment.setArguments(args);
44 | return fragment;
45 | }
46 |
47 | @NonNull
48 | @Override
49 | public Dialog onCreateDialog(Bundle savedInstanceState) {
50 | LayoutInflater layoutInflater = getActivity().getLayoutInflater();
51 |
52 | View view = layoutInflater.inflate(R.layout.fragment_upload_file_dialog, null);
53 | mUnbinder = ButterKnife.bind(this, view);
54 | final String code = getArguments().getString(KEY_UPLOAD_FILE_CODE);
55 | mUploadResultString.setText(String.format(getResources().getString(R.string.upload_desc),
56 | code));
57 | mUploadResultString.setOnLongClickListener(new View.OnLongClickListener() {
58 | @Override
59 | public boolean onLongClick(View view) {
60 | copyToClipboard(getActivity(), code);
61 | if (isUIVisible) {
62 | Toast.makeText(getContext(), getString(R.string.copied_code), Toast
63 | .LENGTH_SHORT).show();
64 | }
65 | return true;
66 | }
67 | });
68 |
69 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
70 | .setView(view)
71 | .setTitle(R.string.upload)
72 | .setPositiveButton(getResources().getString(R.string.ok),
73 | new DialogInterface.OnClickListener() {
74 | public void onClick(DialogInterface dialog, int whichButton) {
75 | dismiss();
76 | }
77 | }
78 | )
79 | .setNegativeButton(getString(R.string.copy), new DialogInterface.OnClickListener() {
80 | @Override
81 | public void onClick(DialogInterface dialogInterface, int i) {
82 | dismiss();
83 | copyToClipboard(getActivity(), code);
84 | if (isUIVisible) {
85 | Toast.makeText(getContext(), getString(R.string.copied_code), Toast
86 | .LENGTH_SHORT).show();
87 | }
88 | }
89 | });
90 |
91 | Dialog dialog = builder.create();
92 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R
93 | .color.black14)));
94 | return dialog;
95 | }
96 |
97 | @Override
98 | public void onDestroyView() {
99 | super.onDestroyView();
100 | mUnbinder.unbind();
101 | }
102 |
103 | @Override
104 | public void onPause() {
105 | super.onPause();
106 | isUIVisible = false;
107 | }
108 |
109 | @Override
110 | public void onResume() {
111 | super.onResume();
112 | isUIVisible = true;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/connection/AndroidJSTPConnection.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.connection;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.net.ConnectivityManager;
8 | import android.net.wifi.WifiManager;
9 | import android.support.v4.content.LocalBroadcastManager;
10 |
11 | import com.metarhia.jstp.connection.Connection;
12 | import com.metarhia.jstp.connection.ConnectionListener;
13 | import com.metarhia.jstp.connection.Message;
14 | import com.metarhia.jstp.connection.RestorationPolicy;
15 | import com.metarhia.jstp.core.Handlers.ManualHandler;
16 | import com.metarhia.jstp.core.JSInterfaces.JSObject;
17 | import com.metarhia.jstp.transport.TCPTransport;
18 | import com.metarhia.metacom.utils.Constants;
19 | import com.metarhia.metacom.utils.NetworkUtils;
20 |
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Queue;
24 | import java.util.UUID;
25 | import java.util.concurrent.ConcurrentHashMap;
26 | import java.util.concurrent.CopyOnWriteArrayList;
27 |
28 | /**
29 | * JSTP connection wrapper for Android
30 | *
31 | * @author lidaamber
32 | * @author lundibundi
33 | */
34 |
35 | public class AndroidJSTPConnection implements ConnectionListener, RestorationPolicy {
36 |
37 | private static final String DEFAULT_CACHE = "defaultCacheTag";
38 |
39 | private static final int STATE_NOT_CONNECTED = 10;
40 | private static final int STATE_CONNECTING = 427;
41 | private static final int STATE_CONNECTED = 500;
42 |
43 | private final LocalBroadcastManager mBroadcastManager;
44 |
45 | private String mApplicationName;
46 |
47 | private final Connection mConnection;
48 |
49 | private final Context mContext;
50 |
51 | private final ConcurrentHashMap> mTaggedCacheCalls;
52 |
53 | private int mConnectionState;
54 |
55 | private final List mListeners;
56 | private boolean mNeedsRestoration;
57 |
58 | public AndroidJSTPConnection(String host, int port, boolean usesSSL, Context context) {
59 | mListeners = new CopyOnWriteArrayList<>();
60 | mTaggedCacheCalls = new ConcurrentHashMap<>();
61 | mConnectionState = STATE_NOT_CONNECTED;
62 |
63 | mNeedsRestoration = true;
64 | mContext = context;
65 |
66 | TCPTransport transport = new TCPTransport(host, port, usesSSL);
67 | mConnection = new Connection(transport, this);
68 | mConnection.addSocketListener(this);
69 |
70 | mBroadcastManager = LocalBroadcastManager.getInstance(mContext);
71 |
72 | initNetworkReceiver();
73 | }
74 |
75 | private void initNetworkReceiver() {
76 | IntentFilter networkActionsFilter =
77 | new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
78 | networkActionsFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
79 |
80 | BroadcastReceiver networkActionsReceiver = new BroadcastReceiver() {
81 | @Override
82 | public void onReceive(Context context, Intent intent) {
83 | if (mApplicationName == null) return;
84 |
85 | else if (!isConnected()) openConnection(mApplicationName);
86 | }
87 | };
88 | mContext.registerReceiver(networkActionsReceiver, networkActionsFilter);
89 | }
90 |
91 | public void openConnection(final String applicationName) {
92 | if (mConnectionState != STATE_NOT_CONNECTED) return;
93 | mConnectionState = STATE_CONNECTING;
94 |
95 | mNeedsRestoration = true;
96 | mApplicationName = applicationName;
97 | checkStartConnection();
98 | }
99 |
100 | @Override
101 | public boolean restore(Connection connection, Queue queue) {
102 | return false;
103 | }
104 |
105 | @Override
106 | public void onTransportAvailable(Connection jstpConnection, String appName,
107 | String sessionID) {
108 | jstpConnection.handshake(appName, new ManualHandler() {
109 | @Override
110 | public void handle(JSObject jsValue) {
111 | mConnectionState = STATE_CONNECTED;
112 | notifyHasConnection();
113 |
114 | if (mListeners != null) {
115 | sendCachedCalls();
116 | for (AndroidJSTPConnectionListener l : mListeners) {
117 | l.onConnectionEstablished(AndroidJSTPConnection.this);
118 | }
119 | }
120 | }
121 | });
122 | }
123 |
124 | private void checkStartConnection() {
125 | if (checkConnection()) {
126 | mConnectionState = STATE_CONNECTING;
127 | mConnection.connect(mApplicationName);
128 | } else {
129 | mConnectionState = STATE_NOT_CONNECTED;
130 | }
131 | }
132 |
133 | private boolean checkConnection() {
134 | return NetworkUtils.isConnectedWifi(mContext)
135 | || isConnectedFast();
136 | }
137 |
138 | private void sendCachedCalls() {
139 | sendCachedCalls(DEFAULT_CACHE);
140 | }
141 |
142 | private void sendCachedCalls(final String tag) {
143 | ConcurrentHashMap cacheCalls = mTaggedCacheCalls.get(tag);
144 | if (cacheCalls == null) return;
145 |
146 | for (final Map.Entry callData : cacheCalls.entrySet()) {
147 | sendCachedCall(tag, callData.getKey(), callData.getValue());
148 | }
149 | }
150 |
151 | private void sendCachedCall(final String tag, final UUID uuid, final CacheCallData callData) {
152 | mConnection.call(callData.mInterfaceName,
153 | callData.mMethodName,
154 | callData.mArgs,
155 | new ManualHandler() {
156 | @Override
157 | public void handle(JSObject message) {
158 | CacheCallData callData = mTaggedCacheCalls.get(tag).remove(uuid);
159 | callData.mManualHandler.handle(message);
160 | }
161 | });
162 | }
163 |
164 | private boolean isConnected() {
165 | return mConnectionState == STATE_CONNECTED;
166 | }
167 |
168 | @Override
169 | public void onConnectionClosed() {
170 | reportConnectionLost();
171 | if (isConnectedFast() && mNeedsRestoration) openConnection(mApplicationName);
172 | else if (needsConnection()) notifyNeedsConnection();
173 | }
174 |
175 | public boolean removeCachedCall(UUID uuid) {
176 | return removeCachedCall(DEFAULT_CACHE, uuid);
177 | }
178 |
179 | private boolean removeCachedCall(String cacheName, UUID uuid) {
180 | if (uuid == null) return false;
181 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheName);
182 | return cachedCalls != null && cachedCalls.remove(uuid) != null;
183 | }
184 |
185 | public void removeCachedCalls(String cacheName) {
186 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheName);
187 | if (cachedCalls != null) cachedCalls.clear();
188 | }
189 |
190 | public void close() {
191 | mNeedsRestoration = false;
192 | mConnection.close();
193 | }
194 |
195 | public interface AndroidJSTPConnectionListener {
196 | void onConnectionEstablished(AndroidJSTPConnection connection);
197 |
198 | void onConnectionLost();
199 | }
200 |
201 | public UUID cacheCall(String interfaceName, final String methodName, List> args,
202 | final ManualHandler handler) {
203 | return cacheCall(DEFAULT_CACHE, interfaceName, methodName, args, handler);
204 | }
205 |
206 | private UUID cacheCall(final String cacheTag, String interfaceName, final String methodName,
207 | List> args, final ManualHandler handler) {
208 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheTag);
209 | if (cachedCalls == null) {
210 | cachedCalls = new ConcurrentHashMap<>();
211 | mTaggedCacheCalls.put(cacheTag, cachedCalls);
212 | }
213 | CacheCallData cacheCallData = new CacheCallData(interfaceName, methodName, args, handler);
214 | final UUID uuid = UUID.randomUUID();
215 | cachedCalls.put(uuid, cacheCallData);
216 | if (isConnected()) {
217 | sendCachedCall(cacheTag, uuid, cacheCallData);
218 | } else if (!NetworkUtils.isConnected(mContext)) {
219 | notifyNeedsConnection();
220 | }
221 | return uuid;
222 | }
223 |
224 | private void reportConnectionLost() {
225 | mConnectionState = STATE_NOT_CONNECTED;
226 | for (AndroidJSTPConnectionListener listener : mListeners) {
227 | listener.onConnectionLost();
228 | }
229 | }
230 |
231 | private void notifyNeedsConnection() {
232 | mBroadcastManager.sendBroadcast(new Intent(Constants.ACTION_NEEDS_CONNECTION));
233 | }
234 |
235 | private void notifyHasConnection() {
236 | mBroadcastManager.sendBroadcast(new Intent(Constants.ACTION_HAS_CONNECTION));
237 | }
238 |
239 | private boolean needsConnection() {
240 | for (Map.Entry> me :
241 | mTaggedCacheCalls.entrySet()) {
242 | if (me.getValue().size() != 0) return true;
243 | }
244 | return false;
245 | }
246 |
247 | public void event(String interfaceName, String methodName, List> args) {
248 | mConnection.event(interfaceName, methodName, args);
249 | }
250 |
251 | public void addCallHandler(String interfaceName, String methodName, ManualHandler callHandler) {
252 | mConnection.setCallHandler(interfaceName, methodName, callHandler);
253 | }
254 |
255 | public void addEventHandler(String interfaceName, String eventName, ManualHandler handler) {
256 | mConnection.addEventHandler(interfaceName, eventName, handler);
257 | }
258 |
259 | private boolean isConnectedFast() {
260 | return NetworkUtils.isConnectedFast(mContext);
261 | }
262 |
263 | @Override
264 | public void onConnected(boolean restored) {
265 | }
266 |
267 | @Override
268 | public void onMessageRejected(JSObject jsObject) {
269 |
270 | }
271 |
272 | @Override
273 | public void onConnectionError(int i) {
274 | }
275 |
276 | public void addListener(AndroidJSTPConnectionListener listener) {
277 | mListeners.add(listener);
278 | }
279 |
280 | public void removeEventHandler(String interfaceName, String methodName, ManualHandler handler) {
281 | mConnection.removeEventHandler(interfaceName, methodName, handler);
282 | }
283 | public void removeListener(AndroidJSTPConnectionListener listener) {
284 | mListeners.remove(listener);
285 | }
286 |
287 | private static class CacheCallData {
288 |
289 | final String mInterfaceName;
290 | final String mMethodName;
291 | final List> mArgs;
292 | final ManualHandler mManualHandler;
293 |
294 | CacheCallData(String interfaceName, String methodName, List> args,
295 | ManualHandler manualHandler) {
296 | this.mArgs = args;
297 | this.mInterfaceName = interfaceName;
298 | this.mMethodName = methodName;
299 | this.mManualHandler = manualHandler;
300 | }
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/connection/Errors.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.connection;
2 |
3 | import android.content.res.Resources;
4 |
5 | import com.metarhia.metacom.R;
6 |
7 | /**
8 | * Error codes holder
9 | *
10 | * @author lidaamber
11 | */
12 |
13 | public class Errors {
14 |
15 | /**
16 | * Local error codes
17 | */
18 | public static final int ERR_FILE_LOAD = 1;
19 |
20 | /**
21 | * API error codes
22 | */
23 | private static final int ERR_ROOM_TAKEN = 30;
24 | private static final int ERR_NOT_IN_CHAT = 31;
25 | private static final int ERR_NO_INTERLOCUTOR = 32;
26 | private static final int ERR_NO_SUCH_FILE = 33;
27 | private static final int ERR_UPLOAD_NOT_STARTED = 34;
28 | private static final int ERR_PREVIOUS_UPLOAD_NOT_FINISHED = 35;
29 |
30 | /**
31 | * Application resources
32 | */
33 | private static Resources sResources;
34 |
35 | /**
36 | * Initializes utils class with app resources
37 | *
38 | * @param resources app resources
39 | */
40 | public static void initResources(Resources resources) {
41 | sResources = resources;
42 | }
43 |
44 | /**
45 | * Gets localized description of error
46 | *
47 | * @param code error code
48 | * @return description of error
49 | */
50 | public static String getErrorByCode(Integer code) {
51 | switch (code) {
52 | case ERR_ROOM_TAKEN:
53 | return sResources.getString(R.string.err_room_taken);
54 | case ERR_NOT_IN_CHAT:
55 | return sResources.getString(R.string.err_not_in_chat);
56 | case ERR_NO_INTERLOCUTOR:
57 | return sResources.getString(R.string.no_interlocutor);
58 | case ERR_NO_SUCH_FILE:
59 | return sResources.getString(R.string.err_no_such_file);
60 | case ERR_FILE_LOAD:
61 | return sResources.getString(R.string.err_file_load);
62 | case ERR_UPLOAD_NOT_STARTED:
63 | return sResources.getString(R.string.err_upload_not_started);
64 | case ERR_PREVIOUS_UPLOAD_NOT_FINISHED:
65 | return sResources.getString(R.string.err_previous_upload_not_finished);
66 | default:
67 | return null;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/connection/OkErrorHandler.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.connection;
2 |
3 | import com.metarhia.jstp.compiler.annotations.handlers.Array;
4 | import com.metarhia.jstp.compiler.annotations.handlers.Handler;
5 | import com.metarhia.jstp.compiler.annotations.handlers.NotNull;
6 | import com.metarhia.jstp.compiler.annotations.handlers.Object;
7 | import com.metarhia.jstp.handlers.ExecutableHandler;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * JSTP handler
13 | *
14 | * @author lidaamber
15 | */
16 |
17 | @Handler(ExecutableHandler.class)
18 | public interface OkErrorHandler {
19 |
20 | @NotNull
21 | @Object("ok")
22 | void onOk(List> args);
23 |
24 | @NotNull
25 | @Object("error")
26 | void onError(@Array(0) Integer errorCode);
27 | }
28 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/BackPressedHandler.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Created by masha on 7/31/17.
5 | */
6 |
7 | public interface BackPressedHandler {
8 | public void handleBackPress();
9 | }
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/ChatReconnectionListener.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Reconnection interface for chat room
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface ChatReconnectionListener {
10 |
11 | /**
12 | * Emitted when connection gets lost
13 | */
14 | void onConnectionLost();
15 |
16 | /**
17 | * Emitted when rejoin passes successfully
18 | */
19 | void onRejoinSuccess(boolean hasInterlocutor);
20 |
21 | /**
22 | * Emitted when rejoin passes with error
23 | *
24 | * @param errorMessage error message
25 | */
26 | void onRejoinError(String errorMessage);
27 | }
28 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/ConnectionCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Callback after connection establishment attempt
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface ConnectionCallback {
10 |
11 | /**
12 | * Called when connection is successfully established
13 | *
14 | * @param connectionID connection id
15 | */
16 | void onConnectionEstablished(int connectionID);
17 |
18 | /**
19 | * Called when server responds error
20 | */
21 | void onConnectionError();
22 | }
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/DownloadFileByCodeListener.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * @author MariaKokshaikina
5 | */
6 |
7 | public interface DownloadFileByCodeListener {
8 |
9 | void downloadByCode(String code);
10 | }
11 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/FileDownloadedListener.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Listener for file downloaded event
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface FileDownloadedListener {
10 |
11 | /**
12 | * Called when file is downloaded successfully
13 | *
14 | * @param path absolute path to file
15 | */
16 | void onFileDownloaded(String path);
17 |
18 | /**
19 | * Called when server responds error
20 | */
21 | void onFileDownloadError();
22 | }
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/FileUploadedCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Callback after file uploading
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface FileUploadedCallback {
10 |
11 | /**
12 | * Called when file is uploaded successfully
13 | *
14 | * @param fileCode code of uploaded file
15 | */
16 | void onFileUploaded(String fileCode);
17 |
18 | /**
19 | * Called when server responds error
20 | *
21 | * @param message error message
22 | */
23 | void onFileUploadError(String message);
24 | }
25 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/JoinRoomCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Callback after chat establishment attempt
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface JoinRoomCallback {
10 |
11 | /**
12 | * Called when chat is established successfully
13 | */
14 | void onJoinedRoom(boolean hasInterlocutor);
15 |
16 | /**
17 | * Called when server responds error
18 | *
19 | * @param errorMessage error message
20 | */
21 | void onJoinError(String errorMessage);
22 | }
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/LeaveRoomCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | /**
4 | * Callback after leaving chat room
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public interface LeaveRoomCallback {
10 |
11 | /**
12 | * Called when user successfully left chat room
13 | */
14 | void onLeavedRoom();
15 |
16 | /**
17 | * Called when server responds error
18 | *
19 | * @param errorMessage error message
20 | */
21 | void onLeaveError(String errorMessage);
22 | }
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/MessageListener.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | import com.metarhia.metacom.models.Message;
4 |
5 | /**
6 | * Incoming messages listener
7 | *
8 | * @author lidaamber
9 | */
10 |
11 | public interface MessageListener {
12 |
13 | /**
14 | * Called when message is received
15 | *
16 | * @param message message received from server
17 | */
18 | void onMessageReceived(Message message);
19 | }
20 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/MessageSentCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.interfaces;
2 |
3 | import com.metarhia.metacom.models.Message;
4 |
5 | /**
6 | * Callback after message sending
7 | *
8 | * @author lidaamber
9 | */
10 |
11 | public interface MessageSentCallback {
12 |
13 | /**
14 | * Called when message is sent successfully
15 | *
16 | * @param message message sent by user
17 | */
18 | void onMessageSent(Message message);
19 |
20 |
21 | /**
22 | * Called when server responds error
23 | *
24 | * @param message error message
25 | */
26 | void onMessageSentError(String message);
27 | }
28 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/ChatRoom.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import android.util.Base64;
4 |
5 | import com.metarhia.jstp.compiler.annotations.handlers.Array;
6 | import com.metarhia.jstp.core.Handlers.ManualHandler;
7 | import com.metarhia.jstp.core.JSInterfaces.JSObject;
8 | import com.metarhia.jstp.handlers.ExecutableHandler;
9 | import com.metarhia.metacom.connection.AndroidJSTPConnection;
10 | import com.metarhia.metacom.connection.Errors;
11 | import com.metarhia.metacom.connection.JSTPOkErrorHandler;
12 | import com.metarhia.metacom.interfaces.ChatReconnectionListener;
13 | import com.metarhia.metacom.interfaces.FileDownloadedListener;
14 | import com.metarhia.metacom.interfaces.FileUploadedCallback;
15 | import com.metarhia.metacom.interfaces.MessageListener;
16 | import com.metarhia.metacom.interfaces.MessageSentCallback;
17 | import com.metarhia.metacom.utils.Constants;
18 | import com.metarhia.metacom.utils.FileUtils;
19 | import com.metarhia.metacom.utils.MainExecutor;
20 |
21 | import java.io.InputStream;
22 | import java.util.ArrayList;
23 | import java.util.LinkedList;
24 | import java.util.List;
25 | import java.util.Queue;
26 |
27 | /**
28 | * MetaCom chat room
29 | *
30 | * @author lidaamber
31 | */
32 |
33 | public class ChatRoom {
34 |
35 | /**
36 | * Chat room name
37 | */
38 | private String mChatRoomName;
39 |
40 | /**
41 | * MetaCom connection
42 | */
43 | private final AndroidJSTPConnection mConnection;
44 |
45 | /**
46 | * Message listener for incoming chat room messages
47 | */
48 |
49 | private MessageListener mMessageListener;
50 |
51 | /**
52 | * Current downloaded file chunks
53 | */
54 | private ArrayList mCurrentFileBuffer;
55 |
56 | /**
57 | * Current downloaded file extension
58 | */
59 | private String mCurrentExtension;
60 |
61 | /**
62 | * Callback for file downloaded
63 | */
64 | private FileDownloadedListener mFileDownloadedListener;
65 |
66 | /**
67 | * Chat reconnection listener
68 | */
69 | private ChatReconnectionListener mChatReconnectionListener;
70 |
71 | /**
72 | * Has interlocutor state
73 | */
74 | private boolean mHasInterlocutor;
75 |
76 | /**
77 | * File upload queue
78 | */
79 | private Queue mFileQueue;
80 |
81 | private ManualHandler mStartHandler;
82 | private ManualHandler mChunkHandler;
83 | private ExecutableHandler mEndHandler;
84 | private ExecutableHandler mChatJoinHandler;
85 | private ExecutableHandler mChatLeaveHandler;
86 | private ExecutableHandler mMessageHandler;
87 |
88 | /**
89 | * Creates new chat room by name
90 | *
91 | * @param chatRoomName chat name
92 | */
93 | ChatRoom(String chatRoomName, AndroidJSTPConnection connection) {
94 | mChatRoomName = chatRoomName;
95 | mConnection = connection;
96 | mCurrentFileBuffer = new ArrayList<>();
97 | mFileQueue = new LinkedList<>();
98 |
99 | initIncomingMessagesListener();
100 | initChatJoinListener();
101 | initChatLeaveListener();
102 | initChatTransferListener();
103 | }
104 |
105 | /**
106 | * Gets chat room name
107 | *
108 | * @return chat room name
109 | */
110 | String getChatRoomName() {
111 | return mChatRoomName;
112 | }
113 |
114 | /**
115 | * Sends message to chat room
116 | *
117 | * @param sentMessage message to be sent
118 | * @param callback callback after message sending (success and error)
119 | */
120 | public void sendMessage(final Message sentMessage, final MessageSentCallback callback) {
121 | List args = new ArrayList<>();
122 | args.add(sentMessage.getContent());
123 | mConnection.cacheCall(Constants.META_COM, "send", args, new JSTPOkErrorHandler(MainExecutor.get()) {
124 | @Override
125 | public void onOk(List> args) {
126 | callback.onMessageSent(sentMessage);
127 | }
128 |
129 | @Override
130 | public void onError(Integer errorCode) {
131 | callback.onMessageSentError(Errors.getErrorByCode(errorCode));
132 | }
133 | });
134 | }
135 |
136 | /**
137 | * Adds new incoming messages listener to chat
138 | *
139 | * @param listener incoming messages listener
140 | */
141 | public void setMessageListener(MessageListener listener) {
142 | mMessageListener = listener;
143 | }
144 |
145 | /**
146 | * Adds message event handler to JSTP connection
147 | */
148 | private void initIncomingMessagesListener() {
149 | mMessageHandler = new ExecutableHandler(MainExecutor.get()) {
150 | @Override
151 | public void run() {
152 | List messagePayload = (List) (message).get("message");
153 | String messageContent = (String) messagePayload.get(0);
154 |
155 | Message message = new Message(MessageType.TEXT, messageContent, true);
156 | if (mMessageListener != null) {
157 | mMessageListener.onMessageReceived(message);
158 | }
159 | }
160 | };
161 | mConnection.addEventHandler(Constants.META_COM, "message", mMessageHandler);
162 | }
163 |
164 | private void initChatJoinListener() {
165 | mChatJoinHandler = new ExecutableHandler(MainExecutor.get()) {
166 | @Override
167 | public void run() {
168 | mHasInterlocutor = true;
169 |
170 | String infoText = Constants.EVENT_CHAT_JOIN;
171 | Message message = new Message(MessageType.INFO, infoText, true);
172 | if (mMessageListener != null) {
173 | mMessageListener.onMessageReceived(message);
174 | }
175 | }
176 | };
177 |
178 | mConnection.addEventHandler(Constants.META_COM, "chatJoin", mChatJoinHandler);
179 | }
180 |
181 | private void initChatLeaveListener() {
182 | mFileQueue.clear();
183 | mChatLeaveHandler = new ExecutableHandler(MainExecutor.get()) {
184 | @Override
185 | public void run() {
186 | mHasInterlocutor = false;
187 |
188 | String infoText = Constants.EVENT_CHAT_LEAVE;
189 | Message message = new Message(MessageType.INFO, infoText, true);
190 | if (mMessageListener != null) {
191 | mMessageListener.onMessageReceived(message);
192 | }
193 | }
194 | };
195 | mConnection.addEventHandler(Constants.META_COM, "chatLeave", mChatLeaveHandler);
196 | }
197 |
198 | private void initChatTransferListener() {
199 | mStartHandler = new ManualHandler() {
200 | @Override
201 | public void handle(JSObject jsObject) {
202 | List messagePayload = (List) (jsObject).get("chatFileTransferStart");
203 | String type = (String) messagePayload.get(0);
204 | mCurrentExtension = (type == null) ? "txt" :
205 | FileUtils.sMimeTypeMap.getExtensionFromMimeType(type);
206 |
207 | mCurrentFileBuffer = new ArrayList<>();
208 | }
209 | };
210 |
211 | mChunkHandler = new ManualHandler() {
212 | @Override
213 | public void handle(JSObject jsObject) {
214 | List messagePayload = (List) (jsObject).get("chatFileTransferChunk");
215 | String fileChunk = (String) messagePayload.get(0);
216 | if (mCurrentFileBuffer != null)
217 | mCurrentFileBuffer.add(Base64.decode(fileChunk, Base64.NO_WRAP));
218 | }
219 | };
220 |
221 | mEndHandler = new ExecutableHandler(MainExecutor.get()) {
222 | @Override
223 | public void run() {
224 | saveFile();
225 | }
226 | };
227 |
228 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferStart", mStartHandler);
229 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferChunk", mChunkHandler);
230 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferEnd", mEndHandler);
231 | }
232 |
233 | void removeAllHandlers() {
234 | mFileQueue.clear();
235 |
236 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferStart", mStartHandler);
237 | mStartHandler = null;
238 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferChunk", mChunkHandler);
239 | mChunkHandler = null;
240 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferEnd", mEndHandler);
241 | mEndHandler = null;
242 | mConnection.removeEventHandler(Constants.META_COM, "chatJoin", mChatJoinHandler);
243 | mChatJoinHandler = null;
244 | mConnection.removeEventHandler(Constants.META_COM, "chatLeave", mChatLeaveHandler);
245 | mChatLeaveHandler = null;
246 | mConnection.removeEventHandler(Constants.META_COM, "message", mMessageHandler);
247 | mMessageHandler = null;
248 | }
249 |
250 | /**
251 | * Uploads file in chat
252 | *
253 | * @param fileStream file to upload
254 | * @param callback callback after file upload (success and error)
255 | */
256 | public void uploadFile(final InputStream fileStream, String mimeType,
257 | final FileUploadedCallback callback) {
258 | if (!mFileQueue.isEmpty()) {
259 | mFileQueue.add(new FileUploadData(fileStream, mimeType, callback));
260 | return;
261 | }
262 |
263 | mFileQueue.add(new FileUploadData(fileStream, mimeType, callback));
264 | uploadNextFromQueue();
265 |
266 | }
267 |
268 | private void uploadNextFromQueue() {
269 | if (mFileQueue.isEmpty()) return;
270 | final FileUploadData data = mFileQueue.peek();
271 | startFileUpload(data.mimeType, new JSTPOkErrorHandler(MainExecutor.get()) {
272 | @Override
273 | public void onOk(List> args) {
274 | FileUtils.uploadSplitFile(data.fileStream,
275 | new FileUtils.FileUploadingInterface() {
276 | @Override
277 | public void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) {
278 | ChatRoom.this.sendChunk(chunk, handler);
279 | }
280 |
281 | @Override
282 | public void endFileUpload(FileUploadedCallback callback) {
283 | ChatRoom.this.endFileUpload(callback);
284 |
285 | }
286 | }, data.callback);
287 | }
288 |
289 | @Override
290 | public void onError(@Array(0) Integer errorCode) {
291 | data.callback.onFileUploadError(Errors.getErrorByCode(errorCode));
292 | mFileQueue.remove();
293 | uploadNextFromQueue();
294 | }
295 | });
296 |
297 | }
298 |
299 | /**
300 | * Starts file upload to server
301 | *
302 | * @param mimeType mime type of the sent file
303 | * @param handler JSTP handler
304 | */
305 | private void startFileUpload(String mimeType, JSTPOkErrorHandler handler) {
306 | List args = new ArrayList<>();
307 | args.add(mimeType);
308 | mConnection.cacheCall(Constants.META_COM, "startChatFileTransfer", args, handler);
309 | }
310 |
311 | /**
312 | * Sends chunk to server
313 | *
314 | * @param chunk chunk to send
315 | * @param handler JSTP handler
316 | */
317 | private void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) {
318 | List args = new ArrayList<>();
319 | args.add(Base64.encodeToString(chunk, Base64.NO_WRAP));
320 | mConnection.cacheCall(Constants.META_COM, "sendFileChunkToChat", args, handler);
321 | }
322 |
323 | /**
324 | * Ends file upload to server
325 | *
326 | * @param callback callback after ending file upload
327 | */
328 | private void endFileUpload(final FileUploadedCallback callback) {
329 | mConnection.cacheCall(Constants.META_COM, "endChatFileTransfer", new ArrayList<>(),
330 | new JSTPOkErrorHandler(MainExecutor.get()) {
331 | @Override
332 | public void onOk(List> args) {
333 | callback.onFileUploaded(null);
334 | mFileQueue.remove();
335 | uploadNextFromQueue();
336 | }
337 |
338 | @Override
339 | public void onError(Integer errorCode) {
340 | callback.onFileUploadError(Errors.getErrorByCode(errorCode));
341 | mFileQueue.remove();
342 | uploadNextFromQueue();
343 | }
344 | });
345 | }
346 |
347 | /**
348 | * Saves currently downloaded file to downloads folder
349 | */
350 | private void saveFile() {
351 | FileUtils.saveFileInDownloads(String.valueOf(mCurrentExtension),
352 | new ArrayList<>(mCurrentFileBuffer),
353 | new FileDownloadedListener() {
354 | @Override
355 | public void onFileDownloaded(String path) {
356 | if (mFileDownloadedListener != null) {
357 | mFileDownloadedListener.onFileDownloaded(path);
358 | }
359 | }
360 |
361 | @Override
362 | public void onFileDownloadError() {
363 | if (mFileDownloadedListener != null) {
364 | mFileDownloadedListener.onFileDownloadError();
365 | }
366 | }
367 | });
368 | }
369 |
370 | /**
371 | * Sets listener on file downloaded event
372 | *
373 | * @param fileDownloadedListener file downloaded listener
374 | */
375 | public void setFileDownloadedListener(FileDownloadedListener fileDownloadedListener) {
376 | mFileDownloadedListener = fileDownloadedListener;
377 | }
378 |
379 | void reportRejoinSuccess(boolean hasInterlocutor) {
380 | mHasInterlocutor = hasInterlocutor;
381 | if (mChatReconnectionListener != null) {
382 | mChatReconnectionListener.onRejoinSuccess(hasInterlocutor);
383 | }
384 | }
385 |
386 | void reportRejoinError(Integer errorCode) {
387 | if (mChatReconnectionListener != null) {
388 | mChatReconnectionListener.onRejoinError(Errors.getErrorByCode(errorCode));
389 | }
390 | }
391 |
392 | void reportConnectionLost() {
393 | mFileQueue.clear();
394 | if (mChatReconnectionListener != null) {
395 | mChatReconnectionListener.onConnectionLost();
396 | }
397 | }
398 |
399 | /**
400 | * Sets chat reconnection listener to specified listener
401 | *
402 | * @param chatReconnectionListener chat reconnection listener
403 | */
404 | public void setChatReconnectionListener(ChatReconnectionListener chatReconnectionListener) {
405 | mChatReconnectionListener = chatReconnectionListener;
406 | }
407 |
408 | /**
409 | * Checks if there is any interlocutor in chat
410 | *
411 | * @return has interlocutor
412 | */
413 | public boolean hasInterlocutor() {
414 | return mHasInterlocutor;
415 | }
416 |
417 | /**
418 | * Sets if there is any interlocutor in chat
419 | *
420 | * @param hasInterlocutor has interlocutor
421 | */
422 | void setHasInterlocutor(boolean hasInterlocutor) {
423 | this.mHasInterlocutor = hasInterlocutor;
424 | }
425 |
426 | private static class FileUploadData {
427 | final InputStream fileStream;
428 | final String mimeType;
429 | final FileUploadedCallback callback;
430 |
431 | FileUploadData(InputStream is, String mimeType, FileUploadedCallback cb) {
432 | this.fileStream = is;
433 | this.mimeType = mimeType;
434 | this.callback = cb;
435 | }
436 | }
437 | }
438 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/ChatRoomsManager.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import com.metarhia.jstp.compiler.annotations.handlers.Array;
4 | import com.metarhia.metacom.connection.AndroidJSTPConnection;
5 | import com.metarhia.metacom.connection.Errors;
6 | import com.metarhia.metacom.connection.JSTPOkErrorHandler;
7 | import com.metarhia.metacom.interfaces.JoinRoomCallback;
8 | import com.metarhia.metacom.interfaces.LeaveRoomCallback;
9 | import com.metarhia.metacom.utils.Constants;
10 | import com.metarhia.metacom.utils.FileUtils;
11 | import com.metarhia.metacom.utils.HistoryCallback;
12 | import com.metarhia.metacom.utils.MainExecutor;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Manager for user connection chats
19 | *
20 | * @author lidaamber
21 | */
22 |
23 | public class ChatRoomsManager implements AndroidJSTPConnection.AndroidJSTPConnectionListener {
24 |
25 | /**
26 | * List of user chat rooms
27 | */
28 | private final List mChatRooms;
29 |
30 | /**
31 | * MetaCom connection
32 | */
33 | private final AndroidJSTPConnection mConnection;
34 |
35 | /**
36 | * Creates new chat rooms manager
37 | */
38 | ChatRoomsManager(AndroidJSTPConnection connection) {
39 | mConnection = connection;
40 | mChatRooms = new ArrayList<>();
41 |
42 | mConnection.addListener(this);
43 | }
44 |
45 | /**
46 | * Adds new chat room by name
47 | *
48 | * @param roomName room name to be added
49 | * @param callback callback after attempt to create chat (success and error)
50 | */
51 | public void addChatRoom(final String roomName, final JoinRoomCallback callback) {
52 | joinRoom(roomName, new JSTPOkErrorHandler(MainExecutor.get()) {
53 | @Override
54 | public void onOk(List args) {
55 |
56 | Boolean hasInterlocutor = (Boolean) args.get(0);
57 |
58 | ChatRoom room = new ChatRoom(roomName, mConnection);
59 | room.setHasInterlocutor(hasInterlocutor);
60 |
61 | mChatRooms.add(room);
62 | callback.onJoinedRoom(hasInterlocutor);
63 | }
64 |
65 | @Override
66 | public void onError(Integer errorCode) {
67 | callback.onJoinError(Errors.getErrorByCode(errorCode));
68 | }
69 | });
70 |
71 | }
72 |
73 | private void joinRoom(String roomName, JSTPOkErrorHandler handler) {
74 | List args = new ArrayList<>();
75 | args.add(roomName);
76 | mConnection.cacheCall(Constants.META_COM, "join", args, handler);
77 | }
78 |
79 | /**
80 | * Gets chat room by name
81 | *
82 | * @param roomName chat name
83 | * @return required chat
84 | */
85 | public ChatRoom getChatRoom(String roomName) {
86 | for (ChatRoom chatRoom : mChatRooms) {
87 | if (chatRoom.getChatRoomName().equals(roomName)) {
88 | return chatRoom;
89 | }
90 | }
91 | return null;
92 | }
93 |
94 | /**
95 | * Leaves chat room and removes char room from chats list
96 | *
97 | * @param chatRoom chatRoom to be removed
98 | */
99 | public void leaveChatRoom(final ChatRoom chatRoom, final LeaveRoomCallback callback) {
100 | mConnection.cacheCall(Constants.META_COM, "leave", new ArrayList<>(),
101 | new JSTPOkErrorHandler(MainExecutor.get()) {
102 | @Override
103 | public void onOk(List> args) {
104 | chatRoom.removeAllHandlers();
105 | mChatRooms.remove(chatRoom);
106 | callback.onLeavedRoom();
107 | }
108 |
109 | @Override
110 | public void onError(Integer errorCode) {
111 | callback.onLeaveError(Errors.getErrorByCode(errorCode));
112 | }
113 | });
114 | }
115 |
116 | @Override
117 | public void onConnectionEstablished(AndroidJSTPConnection connection) {
118 | if (mChatRooms.isEmpty()) return;
119 | for (final ChatRoom room : mChatRooms) {
120 | joinRoom(room.getChatRoomName(), new JSTPOkErrorHandler(MainExecutor.get()) {
121 | @Override
122 | public void onOk(List> args) {
123 | Boolean hasInterlocutor = (Boolean) args.get(0);
124 | room.reportRejoinSuccess(hasInterlocutor);
125 | }
126 |
127 | @Override
128 | public void onError(@Array(0) Integer errorCode) {
129 | room.reportRejoinError(errorCode);
130 | }
131 | });
132 | }
133 |
134 | }
135 |
136 | @Override
137 | public void onConnectionLost() {
138 | MainExecutor.get().execute(new Runnable() {
139 | @Override
140 | public void run() {
141 | if (mChatRooms.isEmpty()) return;
142 |
143 | for (ChatRoom room : mChatRooms) {
144 | room.reportConnectionLost();
145 | }
146 | }
147 | });
148 | }
149 |
150 | public void saveHistory(List messages, HistoryCallback callback) {
151 | StringBuilder messageBuilder = new StringBuilder();
152 | for (Message message : messages) {
153 | if (message.getType() == MessageType.INFO) {
154 | messageBuilder.append(message.getContent() + "\n");
155 | continue;
156 | }
157 |
158 | String sender = message.isIncoming() ? "Interlocutor: " : "Me: ";
159 | messageBuilder.append(sender);
160 | if (message.getType() == MessageType.FILE) {
161 | messageBuilder.append("File");
162 | } else {
163 | messageBuilder.append(message.getContent());
164 | }
165 | messageBuilder.append("\n");
166 | }
167 |
168 | FileUtils.saveMessageHistory(messageBuilder.toString(), callback);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/ConnectionInfoProvider.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Nullable;
5 |
6 | import com.metarhia.metacom.utils.FileUtils;
7 |
8 | import java.io.File;
9 | import java.util.Map;
10 |
11 | /**
12 | * Simple connection info provider that saves and restores user connections for more comfortable
13 | * usage
14 | *
15 | * @author lidaamber
16 | */
17 |
18 | public class ConnectionInfoProvider {
19 |
20 | private static final String CONNECTION_FILENAME = "connectionInfo";
21 |
22 | public static void saveConnectionInfo(Context context, String host, int port) {
23 | File file = new File(context.getFilesDir(), CONNECTION_FILENAME);
24 |
25 | FileUtils.saveConnectionInfo(file, host, port);
26 | }
27 |
28 | @Nullable
29 | public static Map restoreConnectionInfo(Context context) {
30 | File file = new File(context.getFilesDir(), CONNECTION_FILENAME);
31 | if (!file.exists()) return null;
32 | else {
33 | try {
34 | return FileUtils.readConnectionListFromFile(file);
35 | } catch (Exception e) {
36 | return null;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/FilesManager.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import android.util.Base64;
4 |
5 | import com.metarhia.jstp.compiler.annotations.handlers.Array;
6 | import com.metarhia.jstp.core.Handlers.ManualHandler;
7 | import com.metarhia.jstp.core.JSInterfaces.JSObject;
8 | import com.metarhia.jstp.handlers.ExecutableHandler;
9 | import com.metarhia.metacom.connection.AndroidJSTPConnection;
10 | import com.metarhia.metacom.connection.Errors;
11 | import com.metarhia.metacom.connection.JSTPOkErrorHandler;
12 | import com.metarhia.metacom.interfaces.FileDownloadedListener;
13 | import com.metarhia.metacom.interfaces.FileUploadedCallback;
14 | import com.metarhia.metacom.utils.Constants;
15 | import com.metarhia.metacom.utils.FileUtils;
16 | import com.metarhia.metacom.utils.MainExecutor;
17 |
18 | import java.io.InputStream;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * Manager for uploading and downloading files
24 | *
25 | * @author lidaamber
26 | */
27 |
28 | public class FilesManager {
29 |
30 | /**
31 | * MetaCom connection
32 | */
33 | private final AndroidJSTPConnection mConnection;
34 |
35 | /**
36 | * Current downloaded file chunks
37 | */
38 | private ArrayList mCurrentFileBuffer;
39 |
40 | /**
41 | * Current downloaded file extension
42 | */
43 | private String mCurrentExtension;
44 |
45 | /**
46 | * Current downloading file callback
47 | */
48 | private FileDownloadedListener mCurrentCallback;
49 |
50 | /**
51 | * Creates new files manager
52 | */
53 | FilesManager(AndroidJSTPConnection connection) {
54 | mConnection = connection;
55 |
56 | initTransferListener();
57 | }
58 |
59 | /**
60 | * Uploads file to server specified in UserConnection
61 | *
62 | * @param fileStream file to upload
63 | * @param callback callback after file upload (success and error)
64 | */
65 | public void uploadFile(InputStream fileStream, final FileUploadedCallback callback) {
66 | FileUtils.uploadSplitFile(fileStream, new FileUtils.FileUploadingInterface() {
67 | @Override
68 | public void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) {
69 | FilesManager.this.sendChunk(chunk, handler);
70 | }
71 |
72 | @Override
73 | public void endFileUpload(FileUploadedCallback callback) {
74 | FilesManager.this.endFileUpload(callback);
75 | }
76 | }, callback);
77 | }
78 |
79 | /**
80 | * Downloads file from server specified in UserConnection
81 | *
82 | * @param fileCode code of file to download
83 | * @param callback callback after file download (success and error)
84 | */
85 | public void downloadFile(String fileCode, final FileDownloadedListener callback) {
86 | List args = new ArrayList<>();
87 | args.add(fileCode);
88 | mConnection.cacheCall(Constants.META_COM, "downloadFile", args,
89 | new JSTPOkErrorHandler(MainExecutor.get()) {
90 | @Override
91 | public void onOk(List> args) {
92 | mCurrentCallback = callback;
93 | }
94 |
95 | @Override
96 | public void onError(@Array(0) Integer errorCode) {
97 | callback.onFileDownloadError();
98 | }
99 | });
100 | }
101 |
102 | private void initTransferListener() {
103 | mConnection.addEventHandler(Constants.META_COM, "downloadFileStart",
104 | new ManualHandler() {
105 | @Override
106 | public void handle(JSObject jsObject) {
107 | List messagePayload = (List) (jsObject).get("downloadFileStart");
108 | String type = (String) messagePayload.get(0);
109 | mCurrentExtension = (type == null) ? "txt" :
110 | FileUtils.sMimeTypeMap.getExtensionFromMimeType(type);
111 |
112 | mCurrentFileBuffer = new ArrayList<>();
113 | }
114 | });
115 |
116 | mConnection.addEventHandler(Constants.META_COM, "downloadFileChunk",
117 | new ManualHandler() {
118 | @Override
119 | public void handle(JSObject jsObject) {
120 | List messagePayload = (List) (jsObject).get("downloadFileChunk");
121 | String fileChunk = (String) messagePayload.get(0);
122 | if (mCurrentFileBuffer != null)
123 | mCurrentFileBuffer.add(Base64.decode(fileChunk, Base64.NO_WRAP));
124 | }
125 | });
126 |
127 | mConnection.addEventHandler(Constants.META_COM, "downloadFileEnd",
128 | new ExecutableHandler(MainExecutor.get()) {
129 | @Override
130 | public void run() {
131 | FileUtils.saveFileInDownloads(mCurrentExtension, mCurrentFileBuffer,
132 | mCurrentCallback);
133 | }
134 | });
135 | }
136 |
137 | /**
138 | * Sends chunk to server
139 | *
140 | * @param chunk chunk to send
141 | * @param handler JSTP handler
142 | */
143 | private void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) {
144 | List args = new ArrayList<>();
145 | args.add(Base64.encodeToString(chunk, Base64.NO_WRAP));
146 |
147 | mConnection.cacheCall(Constants.META_COM, "uploadFileChunk", args, handler);
148 | }
149 |
150 | /**
151 | * Ends file upload to server
152 | *
153 | * @param callback callback after ending file upload
154 | */
155 | private void endFileUpload(final FileUploadedCallback callback) {
156 | mConnection.cacheCall(Constants.META_COM, "endFileUpload", new ArrayList<>(),
157 | new JSTPOkErrorHandler(MainExecutor.get()) {
158 | @Override
159 | public void onOk(List> args) {
160 | String fileCode = (String) args.get(0);
161 | callback.onFileUploaded(fileCode);
162 | }
163 |
164 | @Override
165 | public void onError(Integer errorCode) {
166 | callback.onFileUploadError(Errors.getErrorByCode(errorCode));
167 | }
168 | });
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/Message.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Message structure
7 | *
8 | * @author lidaamber
9 | */
10 |
11 | public class Message implements Serializable {
12 |
13 | /**
14 | * Type of the message
15 | */
16 | private final MessageType mType;
17 |
18 | /**
19 | * Message content
20 | */
21 | private final String mContent;
22 |
23 | /**
24 | * Shows if message belongs to user or it's incoming
25 | */
26 | private final boolean mIsIncoming;
27 |
28 | /**
29 | * Shows if message is sending or receiving now
30 | */
31 | private boolean mIsWaiting;
32 |
33 | /**
34 | * Creates message with specified type and content
35 | *
36 | * @param type message type
37 | * @param content message content
38 | */
39 | public Message(MessageType type, String content, boolean isIncoming) {
40 | mType = type;
41 | mContent = content;
42 | mIsIncoming = isIncoming;
43 | }
44 |
45 | /**
46 | * Gets message type
47 | *
48 | * @return message type
49 | */
50 | public MessageType getType() {
51 | return mType;
52 | }
53 |
54 | /**
55 | * Gets message content
56 | *
57 | * @return message content
58 | */
59 | public String getContent() {
60 | return mContent;
61 | }
62 |
63 | public boolean isIncoming() {
64 | return mIsIncoming;
65 | }
66 |
67 | public boolean isWaiting() {
68 | return mIsWaiting;
69 | }
70 |
71 | public void setWaiting(boolean isWaiting) {
72 | mIsWaiting = isWaiting;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/MessageType.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | /**
4 | * Type of message
5 | *
6 | * @author lidaamber
7 | */
8 |
9 | public enum MessageType {
10 |
11 | FILE,
12 | TEXT,
13 | INFO
14 | }
15 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/UserConnection.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import com.metarhia.metacom.connection.AndroidJSTPConnection;
4 |
5 | /**
6 | * Connection to certain server mHost and mPort for chat and file exchange purposes
7 | *
8 | * @author lidaamber
9 | */
10 |
11 | public class UserConnection {
12 |
13 | /**
14 | * Identifier of connection
15 | */
16 | private final int mId;
17 |
18 | /**
19 | * Chats manager
20 | */
21 | private final ChatRoomsManager mChatRoomsManager;
22 |
23 | /**
24 | * Files manager
25 | */
26 | private final FilesManager mFilesManager;
27 | private AndroidJSTPConnection mConnection;
28 |
29 | /**
30 | * Creates new user connection
31 | */
32 | UserConnection(int id, AndroidJSTPConnection connection) {
33 | mId = id;
34 |
35 | mChatRoomsManager = new ChatRoomsManager(connection);
36 | mFilesManager = new FilesManager(connection);
37 | mConnection = connection;
38 | }
39 |
40 | /**
41 | * Gets connection ID
42 | *
43 | * @return connection ID
44 | */
45 | public int getId() {
46 | return mId;
47 | }
48 |
49 | /**
50 | * Gets connection chats manager
51 | *
52 | * @return connection chats manager
53 | */
54 | public ChatRoomsManager getChatRoomsManager() {
55 | return mChatRoomsManager;
56 | }
57 |
58 | /**
59 | * Gets connection files manager
60 | *
61 | * @return connection files manager
62 | */
63 | public FilesManager getFilesManager() {
64 | return mFilesManager;
65 | }
66 |
67 | public void closeConnection() {
68 | mConnection.close();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/models/UserConnectionsManager.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.models;
2 |
3 | import android.content.Context;
4 |
5 | import com.metarhia.metacom.connection.AndroidJSTPConnection;
6 | import com.metarhia.metacom.interfaces.ConnectionCallback;
7 | import com.metarhia.metacom.utils.Constants;
8 | import com.metarhia.metacom.utils.MainExecutor;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Manager for user connections to various server hosts and ports
15 | *
16 | * @author lidaamber
17 | */
18 |
19 | public class UserConnectionsManager {
20 |
21 | /**
22 | * User connections manager instance
23 | */
24 | private static UserConnectionsManager instance;
25 |
26 | /**
27 | * List of user connections
28 | */
29 | private final List mUserConnections;
30 |
31 | /**
32 | * Gets user connection manager instance
33 | *
34 | * @return user connection manager instance
35 | */
36 | public static UserConnectionsManager get() {
37 | if (instance == null) {
38 | instance = new UserConnectionsManager();
39 | }
40 |
41 | return instance;
42 | }
43 |
44 | /**
45 | * Creates new user connections manager
46 | */
47 | private UserConnectionsManager() {
48 | mUserConnections = new ArrayList<>();
49 | }
50 |
51 | /**
52 | * Adds new connection with required server host and port
53 | *
54 | * @param context application context
55 | * @param host required server host
56 | * @param port required server port
57 | * @param cb callback after attempt to create connection (success and error)
58 | */
59 | public void addConnection(Context context, String host, int port, final ConnectionCallback cb) {
60 | final AndroidJSTPConnection connection =
61 | new AndroidJSTPConnection(host, port, true, context);
62 | connection.addListener(new AndroidJSTPConnection.AndroidJSTPConnectionListener() {
63 | @Override
64 | public void onConnectionEstablished(final AndroidJSTPConnection connection) {
65 | final AndroidJSTPConnection.AndroidJSTPConnectionListener listener = this;
66 | MainExecutor.get().execute(new Runnable() {
67 | @Override
68 | public void run() {
69 | UserConnection uc = new UserConnection(mUserConnections.size(), connection);
70 | mUserConnections.add(uc);
71 | cb.onConnectionEstablished(uc.getId());
72 | connection.removeListener(listener);
73 | }
74 | });
75 | }
76 |
77 | @Override
78 | public void onConnectionLost() {
79 | final AndroidJSTPConnection.AndroidJSTPConnectionListener listener = this;
80 | MainExecutor.get().execute(new Runnable() {
81 | @Override
82 | public void run() {
83 | cb.onConnectionError();
84 | connection.removeListener(listener);
85 | }
86 | });
87 | }
88 | });
89 | connection.openConnection(Constants.APPLICATION_NAME);
90 | }
91 |
92 | /**
93 | * Gets user connection by ID
94 | *
95 | * @param connectionID connection ID
96 | * @return user connection
97 | */
98 | public UserConnection getConnection(int connectionID) {
99 | if (connectionID >= 0 && connectionID < mUserConnections.size()) {
100 | return mUserConnections.get(connectionID);
101 | } else {
102 | return null;
103 | }
104 | }
105 |
106 | /**
107 | * Removes connection from connection list
108 | *
109 | * @param connection connection to be removed
110 | */
111 | public void removeConnection(UserConnection connection) {
112 | mUserConnections.remove(connection);
113 | connection.closeConnection();
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/Constants.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.content.res.Resources;
4 |
5 | import com.metarhia.metacom.R;
6 |
7 | /**
8 | * Application constants
9 | *
10 | * @author lidaamber
11 | */
12 |
13 | public class Constants {
14 |
15 | private static Resources sResources;
16 |
17 | public static void initResources(Resources resources) {
18 | sResources = resources;
19 |
20 | EVENT_CHAT_JOIN = sResources.getString(R.string.event_chat_join);
21 | EVENT_CHAT_LEAVE = sResources.getString(R.string.event_chat_leave);
22 | DOWNLOAD_FAILED = sResources.getString(R.string.download_failed);
23 | }
24 |
25 | public static final String APPLICATION_NAME = "metarhia.com";
26 |
27 | public static final String META_COM = "metacom";
28 |
29 | public static final String ACTION_NEEDS_CONNECTION = "actionNeedsConnection";
30 | public static final String ACTION_HAS_CONNECTION = "actionHasConnection";
31 |
32 | public static String EVENT_CHAT_JOIN;
33 | public static String EVENT_CHAT_LEAVE;
34 | public static String DOWNLOAD_FAILED;
35 |
36 |
37 | /**
38 | * Creates info message about file path
39 | *
40 | * @param filePath file path
41 | * @return info message about file path
42 | */
43 | public static String composeFilePathInfo(String filePath) {
44 | return String.format(sResources.getString(R.string.file_path), filePath);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.os.Environment;
4 | import android.os.Handler;
5 | import android.os.HandlerThread;
6 | import android.webkit.MimeTypeMap;
7 |
8 | import com.metarhia.metacom.connection.Errors;
9 | import com.metarhia.metacom.connection.JSTPOkErrorHandler;
10 | import com.metarhia.metacom.interfaces.FileDownloadedListener;
11 | import com.metarhia.metacom.interfaces.FileUploadedCallback;
12 |
13 | import java.io.File;
14 | import java.io.FileInputStream;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.io.ObjectInputStream;
19 | import java.io.ObjectOutputStream;
20 | import java.io.OutputStream;
21 | import java.util.ArrayList;
22 | import java.util.HashMap;
23 | import java.util.Iterator;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | /**
28 | * Utils for files manipulations
29 | *
30 | * @author lidaamber
31 | */
32 |
33 | public class FileUtils {
34 |
35 | private static final String FILE_HANDLER_THREAD = "fileHandlerThread";
36 |
37 | public static MimeTypeMap sMimeTypeMap = MimeTypeMap.getSingleton();
38 |
39 | static {
40 | HandlerThread fileHandlerThread = new HandlerThread(FILE_HANDLER_THREAD);
41 | fileHandlerThread.start();
42 | sFileHandler = new Handler(fileHandlerThread.getLooper());
43 | }
44 |
45 | /**
46 | * Handler processing files manipulations
47 | */
48 | private static Handler sFileHandler;
49 |
50 | /**
51 | * Size of chunk to split file
52 | */
53 | private static final int FILE_CHUNK_SIZE = 1024 * 1024;
54 |
55 | /**
56 | * Uploads file to server
57 | *
58 | * @param is file to upload
59 | * @param sendInterface send and end sending methods specific for uploading
60 | * @param callback callback after file upload (success and error)
61 | */
62 | public static void uploadSplitFile(InputStream is, final FileUploadingInterface sendInterface,
63 | final FileUploadedCallback callback) {
64 | splitFile(is, FILE_CHUNK_SIZE, new FileUtils.FileContentsCallback() {
65 | @Override
66 | public void onSplitToChunks(List chunks) {
67 | final Iterator chunkIterator = chunks.iterator();
68 | if (chunkIterator.hasNext()) {
69 | byte[] chunk = chunkIterator.next();
70 | final JSTPOkErrorHandler handler = new JSTPOkErrorHandler(MainExecutor.get()) {
71 | @Override
72 | public void onOk(List> args) {
73 | if (chunkIterator.hasNext()) {
74 | sendInterface.sendChunk(chunkIterator.next(), this);
75 | } else {
76 | sendInterface.endFileUpload(callback);
77 | }
78 | }
79 |
80 | @Override
81 | public void onError(Integer errorCode) {
82 | callback.onFileUploadError(Errors.getErrorByCode(errorCode));
83 | }
84 | };
85 | sendInterface.sendChunk(chunk, handler);
86 | }
87 | }
88 |
89 | @Override
90 | public void onSplitError(Exception e) {
91 | callback.onFileUploadError(Errors.getErrorByCode(Errors.ERR_FILE_LOAD));
92 | }
93 | });
94 | }
95 |
96 | /**
97 | * Splits file into byte[] chunks
98 | *
99 | * @param fileStream file to split
100 | * @param chunkSize size of the chunk
101 | * @param callback callback on file split
102 | */
103 | private static void splitFile(final InputStream fileStream, final int chunkSize,
104 | final FileContentsCallback callback) {
105 | sFileHandler.post(new Runnable() {
106 | @Override
107 | public void run() {
108 | List chunks = new ArrayList<>();
109 | try {
110 | int available = fileStream.available();
111 | int currentBufferSize = available < chunkSize ? available : chunkSize;
112 | byte[] buf = new byte[available < chunkSize ? available : chunkSize];
113 |
114 | while (fileStream.read(buf, 0, currentBufferSize) != -1) {
115 | chunks.add(buf);
116 | available -= buf.length;
117 | currentBufferSize = available < chunkSize ? available : chunkSize;
118 | if (currentBufferSize <= 0) break;
119 | buf = new byte[currentBufferSize];
120 | }
121 | fileStream.close();
122 |
123 | callback.onSplitToChunks(chunks);
124 | } catch (Exception e) {
125 | callback.onSplitError(e);
126 | }
127 | }
128 | });
129 | }
130 |
131 | /**
132 | * Gets downloads storage
133 | */
134 | public static void saveFileInDownloads(String extension, ArrayList buffer,
135 | final FileDownloadedListener callback) {
136 | try {
137 | File path = (Environment.getExternalStorageState() == null ||
138 | !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) ?
139 | Environment.getDataDirectory() :
140 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
141 |
142 | final File file = new File(path, System.currentTimeMillis() + "." + extension);
143 | if (file.createNewFile()) {
144 |
145 | FileUtils.writeChunksToFile(file, buffer,
146 | new FileUtils.FileWritingCallback() {
147 | @Override
148 | public void onWrittenToFile() {
149 | MainExecutor.get().execute(new Runnable() {
150 | @Override
151 | public void run() {
152 | callback.onFileDownloaded(file.getAbsolutePath());
153 | }
154 | });
155 | }
156 |
157 | @Override
158 | public void onWriteError(Exception e) {
159 | callback.onFileDownloadError();
160 | }
161 | });
162 | }
163 |
164 | } catch (IOException e) {
165 | callback.onFileDownloadError();
166 | }
167 | }
168 |
169 | /**
170 | * Writes file chunks to file
171 | *
172 | * @param file file to be written
173 | * @param chunks file chunks
174 | */
175 | private static void writeChunksToFile(final File file, final ArrayList chunks,
176 | final FileWritingCallback callback) {
177 | sFileHandler.post(new Runnable() {
178 | @Override
179 | public void run() {
180 | FileOutputStream stream;
181 | try {
182 | stream = new FileOutputStream(file);
183 | for (byte[] chunk : chunks) {
184 | stream.write(chunk);
185 | }
186 | stream.flush();
187 | stream.close();
188 |
189 | callback.onWrittenToFile();
190 |
191 | } catch (Exception e) {
192 | callback.onWriteError(e);
193 | }
194 | }
195 | });
196 | }
197 |
198 | public static void saveConnectionInfo(File file, String host, int port) {
199 | try {
200 | if (!file.exists()) {
201 | file.createNewFile();
202 | Map infoList = new HashMap<>();
203 | infoList.put(host, port);
204 |
205 | writeConnectionListToFile(infoList, file);
206 | } else {
207 | Map infoList = readConnectionListFromFile(file);
208 |
209 | if (infoList != null) {
210 | infoList.put(host, port);
211 | }
212 |
213 | writeConnectionListToFile(infoList, file);
214 | }
215 | } catch (IOException ignored) {
216 | } catch (ClassNotFoundException ignored) {
217 | }
218 | }
219 |
220 | public static Map readConnectionListFromFile(File file) throws IOException,
221 | ClassNotFoundException {
222 | ObjectInputStream is = new ObjectInputStream(new FileInputStream(file));
223 | Map infoList = (Map) is.readObject();
224 | is.close();
225 |
226 | return infoList;
227 | }
228 |
229 | private static void writeConnectionListToFile(Map infoList, File file) throws
230 | IOException {
231 | ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
232 |
233 | os.writeObject(infoList);
234 | os.flush();
235 | os.close();
236 | }
237 |
238 | public static void saveMessageHistory(String s, HistoryCallback callback) {
239 | try {
240 | String filename = System.currentTimeMillis() + ".txt";
241 |
242 | File path = (Environment.getExternalStorageState() == null ||
243 | !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) ?
244 | Environment.getDataDirectory() :
245 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
246 | final File file = new File(path, filename);
247 | if (file.mkdirs() && file.createNewFile()) {
248 | OutputStream os = new FileOutputStream(file);
249 | os.write(s.getBytes());
250 |
251 | os.flush();
252 | os.close();
253 | callback.onHistorySaved(filename);
254 | }
255 |
256 | } catch (IOException e) {
257 | callback.onSaveError();
258 | }
259 | }
260 |
261 | /**
262 | * Callback for splitting files
263 | */
264 | private interface FileContentsCallback {
265 | /**
266 | * Called when file is split successfully
267 | *
268 | * @param chunks file chunks
269 | */
270 | void onSplitToChunks(List chunks);
271 |
272 | /**
273 | * Called when splitting fails
274 | *
275 | * @param e exception thrown while splitting
276 | */
277 | void onSplitError(Exception e);
278 | }
279 |
280 | /**
281 | * Callback for writing into file
282 | */
283 | private interface FileWritingCallback {
284 |
285 | /**
286 | * Called when content was written successfully
287 | */
288 | void onWrittenToFile();
289 |
290 | /**
291 | * Called when writing failed
292 | *
293 | * @param e exception thrown while writing
294 | */
295 | void onWriteError(Exception e);
296 | }
297 |
298 | /**
299 | * Interface used to describe file uploading
300 | */
301 | public interface FileUploadingInterface {
302 | void sendChunk(byte[] chunk, JSTPOkErrorHandler handler);
303 |
304 | void endFileUpload(FileUploadedCallback callback);
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/HistoryCallback.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | /**
4 | * @author lidaamber
5 | */
6 |
7 | public interface HistoryCallback {
8 | void onHistorySaved(String filename);
9 |
10 | void onSaveError();
11 | }
12 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/KeyboardUtils.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.view.View;
6 | import android.view.inputmethod.InputMethodManager;
7 |
8 | /**
9 | * Created by masha on 9/15/17.
10 | */
11 |
12 | public class KeyboardUtils {
13 |
14 | public static void hideKeyboard(Activity activity) {
15 | View currentFocus = activity.getCurrentFocus();
16 | if (currentFocus != null) {
17 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context
18 | .INPUT_METHOD_SERVICE);
19 | imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/MainExecutor.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.support.annotation.NonNull;
6 |
7 | import java.util.concurrent.Executor;
8 |
9 | /**
10 | * Main thread executor
11 | *
12 | * @author lidaamber
13 | */
14 |
15 | public class MainExecutor implements Executor {
16 |
17 | /**
18 | * Executor instance
19 | */
20 | private static MainExecutor sInstance;
21 |
22 | /**
23 | * Main thread handler
24 | */
25 | private final Handler handler = new Handler(Looper.getMainLooper());
26 |
27 | /**
28 | * Creates new main thread executor
29 | */
30 | private MainExecutor() {
31 | }
32 |
33 | /**
34 | * Gets executor instance
35 | *
36 | * @return main thread executor
37 | */
38 | public static MainExecutor get() {
39 | if (sInstance == null) {
40 | sInstance = new MainExecutor();
41 | }
42 |
43 | return sInstance;
44 | }
45 |
46 | @Override
47 | public void execute(@NonNull Runnable runnable) {
48 | handler.post(runnable);
49 | }
50 | }
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/NetworkUtils.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 |
7 | /**
8 | * Check device's network NetworkUtils and speed
9 | *
10 | * @author emil http://stackoverflow.com/users/220710/emil
11 | * minor changes lundibundi
12 | */
13 | public class NetworkUtils {
14 |
15 | private static NetworkInfo getNetworkInfo(Context context) {
16 | ConnectivityManager cm = (ConnectivityManager)
17 | context.getSystemService(Context.CONNECTIVITY_SERVICE);
18 | return cm.getActiveNetworkInfo();
19 | }
20 |
21 | public static boolean isConnected(Context context) {
22 | NetworkInfo info = NetworkUtils.getNetworkInfo(context);
23 | return (info != null && info.isConnected());
24 | }
25 |
26 | public static boolean isConnectedWifi(Context context) {
27 | NetworkInfo info = NetworkUtils.getNetworkInfo(context);
28 | return (info != null && info.isConnected() &&
29 | info.getType() == ConnectivityManager.TYPE_WIFI);
30 | }
31 |
32 | public static boolean isConnectedFast(Context context) {
33 | return isConnected(context);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/PermissionUtils.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.os.Build;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v4.content.ContextCompat;
8 |
9 | /**
10 | * @author MariaKokshaikina
11 | */
12 |
13 | public class PermissionUtils {
14 |
15 | public static final int ANDROID_VERSION = Build.VERSION.SDK_INT;
16 | public static final int REQUEST_CODE = 1;
17 |
18 | public static boolean checkVersion() {
19 | return ANDROID_VERSION > Build.VERSION_CODES.LOLLIPOP_MR1;
20 | }
21 |
22 | public static boolean checkIfAlreadyHavePermission(Context context) {
23 | int write = ContextCompat.checkSelfPermission(context,
24 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
25 | int read = ContextCompat.checkSelfPermission(context,
26 | android.Manifest.permission.READ_EXTERNAL_STORAGE);
27 | return write == PackageManager.PERMISSION_GRANTED &&
28 | read == PackageManager.PERMISSION_GRANTED;
29 | }
30 |
31 | public static void requestForStoragePermission(Fragment fragment) {
32 | fragment.requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
33 | android.Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/java/com/metarhia/metacom/utils/TextUtils.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.ClipData;
5 | import android.content.ClipboardManager;
6 | import android.content.Context;
7 |
8 | /**
9 | * Created by masha on 7/31/17.
10 | */
11 |
12 | public class TextUtils {
13 |
14 | public static void copyToClipboard(Activity activity, String text) {
15 | ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context
16 | .CLIPBOARD_SERVICE);
17 | ClipData clip = ClipData.newPlainText("copy", text);
18 | clipboard.setPrimaryClip(clip);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_arrow_back_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_attach_file_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_attach_file_grey_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_error_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_file_download_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_file_download_grey_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_file_upload_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_file_upload_grey_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/ic_mail_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/message_in.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
12 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/message_out.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/drawable/metarhia_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/drawable/metarhia_logo.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/activity_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/activity_connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/bottom_notice.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_chat.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
20 |
21 |
30 |
31 |
42 |
43 |
61 |
62 |
63 |
64 |
71 |
72 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_chat_login.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
18 |
19 |
30 |
31 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_connection.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
13 |
14 |
19 |
20 |
29 |
30 |
43 |
44 |
55 |
56 |
67 |
68 |
76 |
77 |
78 |
89 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_download_code_dialog.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
17 |
18 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_files.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
34 |
35 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
15 |
16 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/fragment_upload_file_dialog.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/message_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/message_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
32 |
33 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/message_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/message_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
33 |
34 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @color/orange
6 | @color/black6
7 | @color/white
8 |
9 | #000000
10 | #060606
11 | #1c1c1c
12 |
13 | #FFFFFF
14 |
15 | #1d2217
16 | #888888
17 |
18 | #df9034
19 | #e8b889
20 |
21 |
22 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16sp
4 | 12sp
5 |
6 | 5dp
7 | 10dp
8 | 20dp
9 |
10 | 20dp
11 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MetaCom
3 | HOST_LAST_USED
4 | PORT_LAST_USED
5 | IS_AUTHORIZED
6 |
7 |
8 | Host
9 | Port
10 | Connect
11 | Could\'nt connect to server
12 |
13 | Open via
14 | Server Installation Guide
15 | https://github.com/metarhia/server/blob/master/README.md
16 |
17 |
18 | %1$s:%2$s
19 | Files
20 | Chat
21 |
22 |
23 | Enter download code:
24 | File code
25 | Downloading…
26 | Uploading…
27 | Complete! Click to open file
28 | Take a photo
29 | Open file explorer
30 | File download with current code failed
31 | File upload failed
32 | Select file…
33 | Open file…
34 | Code was copied
35 |
36 |
37 | Join
38 | Chat name
39 |
40 |
41 | New Message
42 | File was sent
43 | Message was copied
44 | Connection lost
45 | There is someone in the chat
46 | Message
47 | Copy
48 | Resend
49 | Send
50 | Connection established
51 |
52 |
53 | Cancel
54 | Download
55 | OK
56 |
57 |
58 | Leave chat
59 | Do you want to leave chat?
60 |
61 | Leave server
62 | Leaving the server will cause aborting all file transitions
63 |
64 | Upload
65 | Your file was uploaded. The code is %s
66 |
67 |
68 | An error occurred while joining chat room. The room is already taken
69 | You are not in chat
70 | There is no one in the chat
71 | No such file
72 | Upload not started
73 | Previous upload not finished
74 |
75 |
76 | Could\'nt load file
77 |
78 |
79 | Somebody has joined the chat
80 | Somebody has left the chat
81 | Your file is saved here: %s
82 | Download failed
83 |
84 |
85 | Permissions
86 | Please, confirm the storage permission to make file transactions.
87 | You can\'t make file transactions without confirming permission
88 | Save messaging history
89 | Do you want to save messaging history?
90 | No
91 | Yes
92 | Saved history in %s in downloads directory
93 | Save history error
94 |
95 |
96 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/MetaCom/app/src/main/res/xml/paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/MetaCom/app/src/test/java/com/metarhia/metacom/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.metarhia.metacom;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/MetaCom/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/MetaCom/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/MetaCom/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/MetaCom/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 08 23:15:30 EET 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/MetaCom/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/MetaCom/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/MetaCom/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Metacommunicator for Android
2 |
3 | ### Now available in [Play Market](https://play.google.com/store/apps/details?id=com.metarhia.metacom) 🎉
4 |
5 | MetaCom is a very secure instant messenger and file exchange tool. Both messaging and file transactions are anonymous and private.
6 |
7 | You shouldn’t doubt about security and reliability as all your messaging and file exchange transactions run on your own server. You can find all the instructions about server installation [here](https://github.com/metarhia/server/blob/master/README.md).
8 |
9 | You can exchange any files without limits of size. Once you upload the file, you receive its code. You can download the file by code from server and it gets deleted from server right after being downloaded.
10 |
11 | Chatting is very secure as well: to chat with someone, you create a room with specific name. One does not simply enter your chat room and see your conversation as there can only be two interlocutors in the room. All the files you transfer in chat are sent right to your interlocutor and are not ever stored on server. Once you leave the chat room, all messaging history is deleted.
12 |
--------------------------------------------------------------------------------