From 76e0b00951e5d0f1c5dbd9630c11eac24729f509 Mon Sep 17 00:00:00 2001 From: Gergo Toth Date: Sat, 11 May 2024 18:27:09 +0200 Subject: [PATCH] Demo Networker app --- .idea/.gitignore | 3 + .idea/compiler.xml | 6 + .idea/deploymentTargetDropDown.xml | 10 + .idea/gradle.xml | 19 ++ .idea/migrations.xml | 10 + .idea/misc.xml | 9 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle.kts | 83 ++++++++ app/proguard-rules.pro | 21 ++ .../networker/ExampleInstrumentedTest.java | 26 +++ app/src/main/AndroidManifest.xml | 34 ++++ .../networker/DeviceDetailsActivity.java | 137 +++++++++++++ .../DeviceDetailsActivityTabbing.java | 51 +++++ .../networker/DummyDeviceCreatorDialog.java | 43 ++++ .../networker/DummyDeviceDetailsFragment.java | 81 ++++++++ .../networker/DummyDeviceGraphsFragment.java | 85 ++++++++ .../networker/DummyDeviceInterfaceDialog.java | 85 ++++++++ .../DummyDeviceSettingsFragment.java | 146 ++++++++++++++ .../com/example/networker/LoginActivity.java | 139 +++++++++++++ .../com/example/networker/MainActivity.java | 184 +++++++++++++++++ .../database/domain/DummyDeviceBackend.java | 166 ++++++++++++++++ .../database/domain/FirebaseConnector.java | 15 ++ .../model/device/DeviceInterface.java | 48 +++++ .../database/model/device/DummyDevice.java | 29 +++ .../model/device/DummyDeviceSettings.java | 27 +++ .../database/model/device/PortForward.java | 51 +++++ .../networker/ui/adapter/DeviceChooser.java | 122 ++++++++++++ .../ui/adapter/DeviceInterfacesList.java | 99 ++++++++++ .../networker/ui/adapter/MetricGraphList.java | 73 +++++++ .../adapter/viewholder/MetricGraphItem.java | 81 ++++++++ .../networker/ui/viewmodel/Device.java | 83 ++++++++ .../ui/viewmodel/DeviceInterface.java | 36 ++++ .../networker/ui/viewmodel/DummyDevice.java | 68 +++++++ .../ui/viewmodel/LiveMetricHistory.java | 42 ++++ .../networker/ui/viewmodel/MetricHistory.java | 64 ++++++ .../main/res/drawable/ic_add_list_item.xml | 5 + app/src/main/res/drawable/ic_devices.xml | 5 + app/src/main/res/drawable/ic_flows.xml | 5 + .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ app/src/main/res/drawable/ic_menu_login.xml | 5 + .../res/layout/activity_device_details.xml | 32 +++ app/src/main/res/layout/activity_login.xml | 83 ++++++++ app/src/main/res/layout/activity_main.xml | 51 +++++ .../layout/fragment_dummydevice_creator.xml | 52 +++++ .../layout/fragment_dummydevice_details.xml | 31 +++ .../layout/fragment_dummydevice_graphs.xml | 17 ++ ...fragment_dummydevice_interface_creator.xml | 59 ++++++ .../layout/fragment_dummydevice_settings.xml | 43 ++++ app/src/main/res/layout/listitem_device.xml | 47 +++++ .../main/res/layout/listitem_interface.xml | 41 ++++ .../main/res/layout/listitem_metric_graph.xml | 24 +++ app/src/main/res/menu/menu_main_activity.xml | 9 + .../menu/menu_main_activity_bottom_nav.xml | 4 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/values-night/themes.xml | 7 + app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/styles.xml | 11 ++ app/src/main/res/values/themes.xml | 9 + app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ .../example/networker/ExampleUnitTest.java | 17 ++ build.gradle.kts | 19 ++ gradle.properties | 23 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle.kts | 18 ++ 82 files changed, 3334 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/example/networker/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/example/networker/DeviceDetailsActivity.java create mode 100644 app/src/main/java/com/example/networker/DeviceDetailsActivityTabbing.java create mode 100644 app/src/main/java/com/example/networker/DummyDeviceCreatorDialog.java create mode 100644 app/src/main/java/com/example/networker/DummyDeviceDetailsFragment.java create mode 100644 app/src/main/java/com/example/networker/DummyDeviceGraphsFragment.java create mode 100644 app/src/main/java/com/example/networker/DummyDeviceInterfaceDialog.java create mode 100644 app/src/main/java/com/example/networker/DummyDeviceSettingsFragment.java create mode 100644 app/src/main/java/com/example/networker/LoginActivity.java create mode 100644 app/src/main/java/com/example/networker/MainActivity.java create mode 100644 app/src/main/java/com/example/networker/database/domain/DummyDeviceBackend.java create mode 100644 app/src/main/java/com/example/networker/database/domain/FirebaseConnector.java create mode 100644 app/src/main/java/com/example/networker/database/model/device/DeviceInterface.java create mode 100644 app/src/main/java/com/example/networker/database/model/device/DummyDevice.java create mode 100644 app/src/main/java/com/example/networker/database/model/device/DummyDeviceSettings.java create mode 100644 app/src/main/java/com/example/networker/database/model/device/PortForward.java create mode 100644 app/src/main/java/com/example/networker/ui/adapter/DeviceChooser.java create mode 100644 app/src/main/java/com/example/networker/ui/adapter/DeviceInterfacesList.java create mode 100644 app/src/main/java/com/example/networker/ui/adapter/MetricGraphList.java create mode 100644 app/src/main/java/com/example/networker/ui/adapter/viewholder/MetricGraphItem.java create mode 100644 app/src/main/java/com/example/networker/ui/viewmodel/Device.java create mode 100644 app/src/main/java/com/example/networker/ui/viewmodel/DeviceInterface.java create mode 100644 app/src/main/java/com/example/networker/ui/viewmodel/DummyDevice.java create mode 100644 app/src/main/java/com/example/networker/ui/viewmodel/LiveMetricHistory.java create mode 100644 app/src/main/java/com/example/networker/ui/viewmodel/MetricHistory.java create mode 100644 app/src/main/res/drawable/ic_add_list_item.xml create mode 100644 app/src/main/res/drawable/ic_devices.xml create mode 100644 app/src/main/res/drawable/ic_flows.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_menu_login.xml create mode 100644 app/src/main/res/layout/activity_device_details.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_dummydevice_creator.xml create mode 100644 app/src/main/res/layout/fragment_dummydevice_details.xml create mode 100644 app/src/main/res/layout/fragment_dummydevice_graphs.xml create mode 100644 app/src/main/res/layout/fragment_dummydevice_interface_creator.xml create mode 100644 app/src/main/res/layout/fragment_dummydevice_settings.xml create mode 100644 app/src/main/res/layout/listitem_device.xml create mode 100644 app/src/main/res/layout/listitem_interface.xml create mode 100644 app/src/main/res/layout/listitem_metric_graph.xml create mode 100644 app/src/main/res/menu/menu_main_activity.xml create mode 100644 app/src/main/res/menu/menu_main_activity_bottom_nav.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/test/java/com/example/networker/ExampleUnitTest.java create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..0c0c338 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..fc8392d --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,83 @@ +plugins { + id("com.android.application") version "8.2.2" apply true + id("androidx.navigation.safeargs") version "2.7.7" apply true + + // Add the Google services Gradle plugin + id("com.google.gms.google-services") +} + +android { + namespace = "com.example.networker" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.networker" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0.1" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + + implementation("io.reactivex.rxjava3:rxjava:3.0.0") + implementation("io.reactivex.rxjava3:rxandroid:3.0.0") + + compileOnly("org.projectlombok:lombok:1.18.30") + annotationProcessor("org.projectlombok:lombok:1.18.30") + + implementation("me.legrange:mikrotik:3.0.7") + + implementation("com.jjoe64:graphview:4.2.2") + implementation("androidx.recyclerview:recyclerview:1.3.2") + implementation("androidx.recyclerview:recyclerview-selection:1.1.0") + implementation("androidx.cardview:cardview:1.0.0") + implementation("androidx.preference:preference:1.2.1") + implementation("androidx.fragment:fragment:1.6.2") + + // Used by at least bottomNavBar + implementation("com.google.android.material:material:1.11.0") + + // Navigation component/graph + implementation("androidx.navigation:navigation-fragment:2.7.7") + implementation("androidx.navigation:navigation-ui:2.7.7") + implementation("androidx.navigation:navigation-dynamic-features-fragment:2.7.7") + androidTestImplementation("androidx.navigation:navigation-testing:2.7.7") + //implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7") + + compileOnly("com.android.tools.build:gradle:8.3.2") + // Import the Firebase BoM + implementation(platform("com.google.firebase:firebase-bom:32.8.1")) + // TODO: Add the dependencies for Firebase products you want to use + // When using the BoM, don't specify versions in Firebase dependencies + // https://firebase.google.com/docs/android/setup#available-libraries + + ////implementation(platform("com.google.firebase:firebase-auth:22.3.1")) + //implementation(platform("com.google.android.gms:play-services-auth:21.1.0")) + implementation("com.google.firebase:firebase-auth") + //implementation("com.google.android.gms:play-services-auth") + + implementation("com.google.firebase:firebase-firestore") + //implementation("com.android.support:multidex:1.0.3") + implementation("com.google.firebase:firebase-database") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/networker/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/networker/ExampleInstrumentedTest.java new file mode 100644 index 0000000..cecd58a --- /dev/null +++ b/app/src/androidTest/java/com/example/networker/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.networker; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.networker", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7a253b3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/DeviceDetailsActivity.java b/app/src/main/java/com/example/networker/DeviceDetailsActivity.java new file mode 100644 index 0000000..c6bbb7b --- /dev/null +++ b/app/src/main/java/com/example/networker/DeviceDetailsActivity.java @@ -0,0 +1,137 @@ +package com.example.networker; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.viewpager2.widget.ViewPager2; + +import com.example.networker.database.domain.DummyDeviceBackend; +import com.example.networker.ui.viewmodel.DeviceInterface; +import com.example.networker.ui.viewmodel.DummyDevice; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.util.HashMap; + +public class DeviceDetailsActivity extends AppCompatActivity { + private static final String LOG_TAG = DeviceDetailsActivity.class.getName(); + + ViewPager2 viewPager; + TabLayout tabChooser; + + private int deviceMonitorListGridNumber = 1; + private DummyDeviceBackend dbBackend; + + DeviceDetailsActivityTabbing tabAdapter; + private String deviceId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_device_details); + + viewPager = findViewById(R.id.viewpager); + tabChooser = findViewById(R.id.tabChooser); + + dbBackend = new DummyDeviceBackend(); + + if (savedInstanceState != null) { + deviceId = savedInstanceState.getString(MainActivity.DEVICEDETAILS_PARAM_DEVICE_ID); + } + else { + deviceId = getIntent().getStringExtra(MainActivity.DEVICEDETAILS_PARAM_DEVICE_ID); + } + + //Serializable deviceSerializable = getIntent().getSerializableExtra(MainActivity.DEVICEDETAILS_PARAM_DEVICE); + //if (deviceSerializable != null) { + if (deviceId != null) { + //dummyDevice = DummyDevice.fromMap((HashMap) deviceSerializable); + dbBackend.getUserDevice(MainActivity.getDbCurrentUser().getId(), deviceId).subscribe( + devicePair -> { + DummyDevice dummyDevice = DummyDevice.fromModel(devicePair.second); + + Log.i(LOG_TAG, String.valueOf(dummyDevice)); + if (dummyDevice != null) { + Log.i(LOG_TAG, "1" + String.valueOf(dummyDevice.getSettings())); + if (dummyDevice.getSettings() != null) { + Log.i(LOG_TAG, "2" + String.valueOf(dummyDevice.getSettings().getInterfaceList())); + if (dummyDevice.getSettings().getInterfaceList() != null) { + for (com.example.networker.database.model.device.DeviceInterface deviceInterface : dummyDevice.getSettings().getInterfaceList()) { + Log.i(LOG_TAG, "3" + String.valueOf(deviceInterface)); + if (deviceInterface != null) { + Log.i(LOG_TAG, "4" + String.valueOf(deviceInterface.getAddress())); + } + } + } + } + } + + tabAdapter = new DeviceDetailsActivityTabbing(this, dummyDevice); + setupFragments(); + } + ); + } + else { + tabAdapter = new DeviceDetailsActivityTabbing(this); + setupFragments(); + } + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(MainActivity.DEVICEDETAILS_PARAM_DEVICE_ID, deviceId); + } + + private void setupFragments() { + viewPager.setAdapter(tabAdapter); + new TabLayoutMediator(tabChooser, viewPager, + (tab, position) -> tab.setText(tabAdapter.getTabNames().get(position)) + ).attach(); + + // DummyDeviceDetailsFragment + getSupportFragmentManager().setFragmentResultListener( + DummyDeviceDetailsFragment.RESULT_DEVICE, + this, + (requestKey, bundle) -> { + HashMap deviceMap = (HashMap) bundle.getSerializable(DummyDeviceDetailsFragment.RESULT_DEVICE); + + saveDevice(DummyDevice.fromMap(deviceMap)); + finish(); + } + ); + + // DummyDeviceSettingsFragment + getSupportFragmentManager().setFragmentResultListener( + DummyDeviceSettingsFragment.RESULT_INTERFACE, + this, + (requestKey, bundle) -> { + HashMap interfaceMap = (HashMap) bundle.getSerializable(DummyDeviceSettingsFragment.RESULT_INTERFACE); + + addNewInterface(DeviceInterface.fromMap(interfaceMap)); + } + ); + getSupportFragmentManager().setFragmentResultListener( + DummyDeviceSettingsFragment.DELETE_INTERFACE, + this, + (requestKey, bundle) -> { + int interfacePos = bundle.getInt(DummyDeviceSettingsFragment.DELETE_INTERFACE); + deleteInterface(interfacePos); + } + ); + } + + protected void saveDevice(DummyDevice device) { + dbBackend.setUserDevice(MainActivity.getDbCurrentUser().getId(), deviceId, device.toModel()); + } + + protected void addNewInterface(com.example.networker.database.model.device.DeviceInterface deviceInterface) { + dbBackend.addInterface(MainActivity.getDbCurrentUser().getId(), deviceId, deviceInterface); + } + + protected void deleteInterface(int position) { + dbBackend.deleteInterface(MainActivity.getDbCurrentUser().getId(), deviceId, position); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/DeviceDetailsActivityTabbing.java b/app/src/main/java/com/example/networker/DeviceDetailsActivityTabbing.java new file mode 100644 index 0000000..ede38b0 --- /dev/null +++ b/app/src/main/java/com/example/networker/DeviceDetailsActivityTabbing.java @@ -0,0 +1,51 @@ +package com.example.networker; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.example.networker.ui.viewmodel.DummyDevice; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public class DeviceDetailsActivityTabbing extends FragmentStateAdapter { + @NonNull + private final FragmentActivity activity; + private final DummyDevice device; + private static final String[] tabNames = new String[] { + "Connection", "Settings", "Monitor" + }; + private static final Function[] fragmentClasses = new Function[] { + device -> new DummyDeviceDetailsFragment((DummyDevice) device), + device -> new DummyDeviceSettingsFragment((DummyDevice) device), + device -> new DummyDeviceGraphsFragment((DummyDevice) device) + }; + + public DeviceDetailsActivityTabbing(@NonNull FragmentActivity fragmentActivity, DummyDevice device) { + super(fragmentActivity); + activity = fragmentActivity; + this.device = device; + } + + public DeviceDetailsActivityTabbing(@NonNull FragmentActivity fragmentActivity) { + this(fragmentActivity, new DummyDevice()); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragmentClasses[position].apply(device); + } + + @Override + public int getItemCount() { + return Math.min(tabNames.length, fragmentClasses.length); + } + + public List getTabNames() { + return Arrays.asList(tabNames); + } +} diff --git a/app/src/main/java/com/example/networker/DummyDeviceCreatorDialog.java b/app/src/main/java/com/example/networker/DummyDeviceCreatorDialog.java new file mode 100644 index 0000000..e68fdfc --- /dev/null +++ b/app/src/main/java/com/example/networker/DummyDeviceCreatorDialog.java @@ -0,0 +1,43 @@ +package com.example.networker; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.example.networker.ui.viewmodel.DummyDevice; + +public class DummyDeviceCreatorDialog extends DialogFragment { + public static final String RESULT_DUMMYDEVICE = DummyDeviceCreatorDialog.class.getName() + "_device"; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.fragment_dummydevice_creator, container, false); + + EditText nameInput = layout.findViewById(R.id.name); + EditText locationUriInput = layout.findViewById(R.id.locationUri); + Button submitBtn = layout.findViewById(R.id.login_btn); + + DummyDevice device = new DummyDevice(); + + locationUriInput.setText(device.getLocationUri()); + + submitBtn.setOnClickListener(l -> { + device.setName(nameInput.getText().toString()); + + Bundle resultBundle = new Bundle(); + resultBundle.putSerializable(RESULT_DUMMYDEVICE, device.toMap()); + getParentFragmentManager().setFragmentResult(RESULT_DUMMYDEVICE, resultBundle); + }); + + + + return layout; + } +} diff --git a/app/src/main/java/com/example/networker/DummyDeviceDetailsFragment.java b/app/src/main/java/com/example/networker/DummyDeviceDetailsFragment.java new file mode 100644 index 0000000..49e6be6 --- /dev/null +++ b/app/src/main/java/com/example/networker/DummyDeviceDetailsFragment.java @@ -0,0 +1,81 @@ +package com.example.networker; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import com.example.networker.ui.viewmodel.DummyDevice; + + +public class DummyDeviceDetailsFragment extends Fragment { + private static final String LOG_TAG = DummyDeviceDetailsFragment.class.getName(); + public static final String RESULT_DEVICE = DummyDeviceDetailsFragment.class.getName() + "_device"; + private DummyDevice device; + private EditText nameInput; + private EditText addressInput; + private Button saveBtn; + + public DummyDeviceDetailsFragment() { + super(); + device = null; + } + + public DummyDeviceDetailsFragment(DummyDevice device) { + super(); + this.device = device; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_dummydevice_details, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + nameInput = view.findViewById(R.id.name); + addressInput = view.findViewById(R.id.address); + saveBtn = view.findViewById(R.id.save_btn); + + saveBtn.setOnClickListener(l -> saveDevice()); + + bindToDevice(); + } + + public void bindToDevice() { + if (device != null) { + if (nameInput != null) { + nameInput.setText(device.getName()); + } + if (addressInput != null) { + addressInput.setText(device.getLocationUri()); + } + } + } + + public void saveDevice() { + if (device != null) { + device.setName(nameInput.getText().toString()); + + Bundle resultBundle = new Bundle(); + resultBundle.putSerializable(RESULT_DEVICE, device.toMap()); + getParentFragmentManager().setFragmentResult(RESULT_DEVICE, resultBundle); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/DummyDeviceGraphsFragment.java b/app/src/main/java/com/example/networker/DummyDeviceGraphsFragment.java new file mode 100644 index 0000000..3c14baa --- /dev/null +++ b/app/src/main/java/com/example/networker/DummyDeviceGraphsFragment.java @@ -0,0 +1,85 @@ +package com.example.networker; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.ui.adapter.MetricGraphList; +import com.example.networker.ui.viewmodel.LiveMetricHistory; +import com.example.networker.ui.viewmodel.DummyDevice; + + +public class DummyDeviceGraphsFragment extends Fragment { + private static final String LOG_TAG = DummyDeviceGraphsFragment.class.getName(); + private DummyDevice device; + private RecyclerView graphListView; + private final int graphListViewGridNumber = 1; + private MetricGraphList metricList; + private Handler handler; + + public DummyDeviceGraphsFragment() { + super(); + device = null; + } + + public DummyDeviceGraphsFragment(DummyDevice device) { + super(); + this.device = device; + } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_dummydevice_graphs, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + graphListView = getView().findViewById(R.id.graphList); + + graphListView.setLayoutManager(new GridLayoutManager(view.getContext(), graphListViewGridNumber)); + metricList = new MetricGraphList<>(view.getContext(), graphListView, 40); + + addGraph(); + } + + public void addGraph() { + LiveMetricHistory metricHistory = new LiveMetricHistory<>(""); + metricList.addMetric(metricHistory); + metricHistory.listenUpdates().subscribe(item -> { + metricList.notifyDataSetChanged(); + //Log.i(LOG_TAG, item.toString()); + }); + + Handler currentHandler = new Handler(Looper.getMainLooper()); + Runnable addNewMetric = new Runnable() { + @Override + public void run() { + if (handler != currentHandler) { + return; + } + metricHistory.appendData(Math.exp(Math.random()+2)); + handler.postAtTime(this, System.currentTimeMillis()+1000); + handler.postDelayed(this, 1000); + } + }; + handler = currentHandler; + addNewMetric.run(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/DummyDeviceInterfaceDialog.java b/app/src/main/java/com/example/networker/DummyDeviceInterfaceDialog.java new file mode 100644 index 0000000..1a857b6 --- /dev/null +++ b/app/src/main/java/com/example/networker/DummyDeviceInterfaceDialog.java @@ -0,0 +1,85 @@ +package com.example.networker; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.example.networker.ui.viewmodel.DeviceInterface; + +import java.util.function.Function; + +public class DummyDeviceInterfaceDialog extends DialogFragment { + private static final String LOG_TAG = DummyDeviceInterfaceDialog.class.getName(); + public static final String RESULT_INTERFACE = DummyDeviceInterfaceDialog.class.getName() + "_interface"; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.fragment_dummydevice_interface_creator, container, false); + + Spinner typeInput = layout.findViewById(R.id.type); + EditText ipAddressInput = layout.findViewById(R.id.ip_address); + EditText netmaskInput = layout.findViewById(R.id.netmask); + Button submitBtn = layout.findViewById(R.id.login_btn); + + DeviceInterface.Type[] types = { + DeviceInterface.Type.WAN, + DeviceInterface.Type.LAN + }; + + typeInput.setAdapter( + new ArrayAdapter( + getActivity(), + android.R.layout.simple_spinner_item, + types + ) + ); + + DeviceInterface deviceInterface = new DeviceInterface(); + Function isAddressValid = (address) -> Patterns.IP_ADDRESS.matcher(address).matches(); + + ipAddressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { } + @Override + public void afterTextChanged(Editable s) { + Log.i(LOG_TAG, String.valueOf(isAddressValid.apply(s.toString()))); + + if (isAddressValid.apply(s.toString())) { + ipAddressInput.setTextColor(0xFF00E000); + } + else { + ipAddressInput.setTextColor(0xFFFF0000); + } + } + }); + + submitBtn.setOnClickListener(l -> { + if (isAddressValid.apply(ipAddressInput.getText().toString())) { + deviceInterface.setInterfaceType(types[typeInput.getSelectedItemPosition()]); + deviceInterface.setAddress(ipAddressInput.getText().toString()); + deviceInterface.setMaskLength(Integer.parseInt(netmaskInput.getText().toString())); + + Bundle resultBundle = new Bundle(); + resultBundle.putSerializable(RESULT_INTERFACE, deviceInterface.toMap()); + getParentFragmentManager().setFragmentResult(RESULT_INTERFACE, resultBundle); + } + }); + + return layout; + } +} diff --git a/app/src/main/java/com/example/networker/DummyDeviceSettingsFragment.java b/app/src/main/java/com/example/networker/DummyDeviceSettingsFragment.java new file mode 100644 index 0000000..57eef69 --- /dev/null +++ b/app/src/main/java/com/example/networker/DummyDeviceSettingsFragment.java @@ -0,0 +1,146 @@ +package com.example.networker; + +import android.app.AlertDialog; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.database.model.device.DeviceInterface; +import com.example.networker.ui.adapter.DeviceInterfacesList; +import com.example.networker.ui.viewmodel.DummyDevice; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.util.HashMap; + + +public class DummyDeviceSettingsFragment extends Fragment { + private class OwnDeviceInterfacesList extends DeviceInterfacesList { + public OwnDeviceInterfacesList(Context context, RecyclerView interfaceSelectorList) { + super(context, interfaceSelectorList); + } + + @Override + public void onBindViewHolder(@NonNull DeviceInterfacesList.ViewHolder holder, int position) { + DeviceInterface deviceIf = getFilteredInterfaceList().get(position); + if (deviceIf != null) { + holder.itemView.setOnLongClickListener( + v -> { + new AlertDialog.Builder(getActivity()) + .setTitle("Delete interface") + .setMessage("Are you sure you want to delete interface with IP '" + + String.valueOf(deviceIf.getAddress()) +"'?") + .setPositiveButton(android.R.string.yes, + (dialog, which) -> { + DummyDeviceSettingsFragment.this.deleteInterface(position); + }) + .setNegativeButton(android.R.string.no, null) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + return true; + } + ); + } + super.onBindViewHolder(holder, position); + } + } + + private static final String LOG_TAG = DummyDeviceSettingsFragment.class.getName(); + public static final String RESULT_INTERFACE = DummyDeviceSettingsFragment.class.getName() + "_interface"; + public static final String DELETE_INTERFACE = DummyDeviceSettingsFragment.class.getName() + "_del_interface"; + private DummyDevice device; + private RecyclerView settingListView; + private FloatingActionButton addSettingBtn; + + private DeviceInterfacesList interfacesList; + private final int settingListViewGridNumber = 1; + + public DummyDeviceSettingsFragment() { + super(); + device = null; + } + + public DummyDeviceSettingsFragment(DummyDevice device) { + super(); + this.device = device; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_dummydevice_settings, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + settingListView = getView().findViewById(R.id.setting_list); + addSettingBtn = getView().findViewById(R.id.add_setting_btn); + + settingListView.setLayoutManager(new GridLayoutManager(view.getContext(), settingListViewGridNumber)); + interfacesList = new OwnDeviceInterfacesList(getActivity(), settingListView); + + addSettingBtn.setOnClickListener(l -> showNewInterfaceDialog()); + + bindToDevice(); + } + + public void bindToDevice() { + if (device != null) { + device.getSettings().getInterfaceList().forEach(interfacesList::addInterface); + } + } + + public void showNewInterfaceDialog() { + DialogFragment dialogFragment = new DummyDeviceInterfaceDialog(); + dialogFragment.show(getActivity().getSupportFragmentManager(), getActivity().getClass().getName()); + + getActivity().getSupportFragmentManager().setFragmentResultListener( + DummyDeviceInterfaceDialog.RESULT_INTERFACE, + this, + (requestKey, bundle) -> { + HashMap deviceInterfaceMap = (HashMap) bundle.getSerializable(DummyDeviceInterfaceDialog.RESULT_INTERFACE); + + com.example.networker.ui.viewmodel.DeviceInterface deviceInterface = com.example.networker.ui.viewmodel.DeviceInterface.fromMap(deviceInterfaceMap); + + addInterface(deviceInterface); + dialogFragment.dismiss(); + } + ); + } + + private void addInterface(com.example.networker.ui.viewmodel.DeviceInterface deviceInterface) { + if (interfacesList != null) { + interfacesList.addInterface(deviceInterface); + + Bundle resultBundle = new Bundle(); + resultBundle.putSerializable(RESULT_INTERFACE, deviceInterface.toMap()); + getParentFragmentManager().setFragmentResult(RESULT_INTERFACE, resultBundle); + } + } + + private void deleteInterface(int position) { + if (interfacesList != null) { + interfacesList.deleteInterface(position); + + Bundle resultBundle = new Bundle(); + resultBundle.putInt(DELETE_INTERFACE, position); + getParentFragmentManager().setFragmentResult(DELETE_INTERFACE, resultBundle); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/LoginActivity.java b/app/src/main/java/com/example/networker/LoginActivity.java new file mode 100644 index 0000000..c42aec9 --- /dev/null +++ b/app/src/main/java/com/example/networker/LoginActivity.java @@ -0,0 +1,139 @@ +package com.example.networker; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.example.networker.database.domain.DummyDeviceBackend; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; + +public class LoginActivity extends AppCompatActivity { + private static final String LOG_TAG = LoginActivity.class.getName(); + private static final String PREF_KEY = LoginActivity.class.getPackage().toString(); + private static final String PREF_STICKY_CREDS = "sticky_creds"; + private SharedPreferences preferences; + private FirebaseAuth mAuth; + private final DummyDeviceBackend dbBackend = new DummyDeviceBackend(); + private CheckBox stickyCredsInput; + private EditText emailInput; + private EditText passwordInput; + + @Override + protected void onStart() { + super.onStart(); + + if (getStickyCredEmail() != null) { + emailInput.setText(getStickyCredEmail()); + stickyCredsInput.setChecked(true); + } + if (getStickyCredPassword() != null) { + passwordInput.setText(getStickyCredPassword()); + stickyCredsInput.setChecked(true); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + mAuth = FirebaseAuth.getInstance(); + preferences = getSharedPreferences(PREF_KEY, MODE_PRIVATE); + + emailInput = findViewById(R.id.email); + passwordInput = findViewById(R.id.password); + stickyCredsInput = findViewById(R.id.sticky_creds); + Button loginBtn = findViewById(R.id.login_btn); + Button registerBtn = findViewById(R.id.register_btn); + + loginBtn.setOnClickListener( + l -> login( + emailInput.getText().toString(), + passwordInput.getText().toString(), + stickyCredsInput.isChecked() + ) + ); + registerBtn.setOnClickListener( + l -> register( + emailInput.getText().toString(), + passwordInput.getText().toString(), + stickyCredsInput.isChecked() + ) + ); + } + + @Override + protected void onPause() { + if (stickyCredsInput != null && !stickyCredsInput.isChecked()) { + setStickyCreds(null, null); + } + super.onPause(); + } + + public void login(String email, String password, boolean isStickyCred) { + if (getStickyCredEmail() == null && getStickyCredPassword() == null && isStickyCred) { + setStickyCreds(email, password); + } + + mAuth.signInWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + if (isStickyCred) { + setStickyCreds(email, password); + } + + Intent dst = new Intent(LoginActivity.this, MainActivity.class); + startActivity(dst); + } + else { + Toast toast = Toast.makeText(LoginActivity.this, + "Login failed; " + String.valueOf(task.getException().getMessage()), Toast.LENGTH_LONG); + toast.show(); + } + } + }); + } + + public void register(String email, String password, boolean isStickyCred) { + mAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener(this, new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + login(email, password, isStickyCred); + } + else { + Toast toast = Toast.makeText(LoginActivity.this, + "Registration failed; " + String.valueOf(task.getException().getMessage()), Toast.LENGTH_LONG); + toast.show(); + } + } + }); + } + + public void setStickyCreds(String email, String password) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(PREF_STICKY_CREDS+"_email", email); + editor.putString(PREF_STICKY_CREDS+"_password", password); + editor.apply(); + } + + public String getStickyCredEmail() { + return preferences.getString(PREF_STICKY_CREDS+"_email", null); + } + public String getStickyCredPassword() { + return preferences.getString(PREF_STICKY_CREDS+"_password", null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/MainActivity.java b/app/src/main/java/com/example/networker/MainActivity.java new file mode 100644 index 0000000..9635484 --- /dev/null +++ b/app/src/main/java/com/example/networker/MainActivity.java @@ -0,0 +1,184 @@ +package com.example.networker; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Pair; +import android.view.Menu; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.database.domain.DummyDeviceBackend; +import com.example.networker.database.model.device.DummyDevice; +import com.example.networker.ui.adapter.DeviceChooser; +import com.example.networker.ui.viewmodel.Device; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.DocumentReference; + +import java.util.HashMap; +import java.util.Map; + +public class MainActivity extends AppCompatActivity { + private class OwnDeviceChooser extends DeviceChooser { + public OwnDeviceChooser(Context context, RecyclerView deviceSelectorList) { + super(context, deviceSelectorList); + } + + @Override + public void onBindViewHolder(@NonNull DeviceChooser.ViewHolder holder, int position) { + Device device = getFilteredDeviceList().get(position); + if (device instanceof com.example.networker.ui.viewmodel.DummyDevice) { + com.example.networker.ui.viewmodel.DummyDevice dummyDevice = (com.example.networker.ui.viewmodel.DummyDevice) device; + + holder.itemView.setOnClickListener(null); + holder.itemView.setOnClickListener( + l -> showDummyDeviceDetails(dummyDevice) + ); + holder.itemView.setOnLongClickListener( + v -> { + new AlertDialog.Builder(MainActivity.this) + .setTitle("Delete device") + .setMessage("Are you sure you want to delete device '" + + String.valueOf(dummyDevice.getName()) +"'?") + .setPositiveButton(android.R.string.yes, + (dialog, which) -> { + if (deviceIds.containsKey(dummyDevice)) { + dbBackend.deleteUserDevice(dbCurrentUser.getId(), deviceIds.get(dummyDevice)); + deviceChooserAdapter.deleteDevice(dummyDevice); + } + }) + .setNegativeButton(android.R.string.no, null) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + return true; + } + ); + } + super.onBindViewHolder(holder, position); + } + } + + private static final String LOG_TAG = MainActivity.class.getName(); + public static final String DEVICEDETAILS_PARAM_DEVICE = "device"; + public static final String DEVICEDETAILS_PARAM_DEVICE_ID = "device_id"; + + RecyclerView deviceSelectorList; + DeviceChooser deviceChooserAdapter; + + private final DummyDeviceBackend dbBackend = new DummyDeviceBackend(); + private DocumentReference dbCurrentUser; + private static Map deviceIds; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + dbCurrentUser = getDbCurrentUser(); + if (dbCurrentUser == null) { + finish(); + return; + } + deviceIds = new HashMap<>(); + + setContentView(R.layout.activity_main); + setSupportActionBar(findViewById(R.id.toolbar)); + + initDeviceSelector(); + } + + private void initDeviceSelector() { + deviceSelectorList = findViewById(R.id.deviceSelectorList); + deviceSelectorList.setLayoutManager(new GridLayoutManager(this, 1)); + + deviceChooserAdapter = new OwnDeviceChooser(this, deviceSelectorList); + populateDeviceList(); + + findViewById(R.id.addNewDeviceBtn).setOnClickListener( + view -> showNewDeviceScreen() + ); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main_activity, menu); + //return super.onCreateOptionsMenu(menu); + return true; + } + + + public void showNewDeviceScreen() { + DialogFragment dialogFragment = new DummyDeviceCreatorDialog(); + dialogFragment.show(getSupportFragmentManager(), MainActivity.class.getName()); + + getSupportFragmentManager().setFragmentResultListener( + DummyDeviceCreatorDialog.RESULT_DUMMYDEVICE, + this, + (requestKey, bundle) -> { + HashMap dummyDeviceMap = (HashMap) bundle.getSerializable(DummyDeviceCreatorDialog.RESULT_DUMMYDEVICE); + + com.example.networker.ui.viewmodel.DummyDevice device = com.example.networker.ui.viewmodel.DummyDevice.fromMap(dummyDeviceMap); + + String deviceId = addDevice(device); + deviceIds.put(device, deviceId); + deviceChooserAdapter.addDevice(device); + + dialogFragment.dismiss(); + showDummyDeviceDetails(device); + } + ); + } + + public void showDummyDeviceDetails(com.example.networker.ui.viewmodel.DummyDevice device) { + Intent dst = new Intent(this, DeviceDetailsActivity.class); + + dst.putExtra(DEVICEDETAILS_PARAM_DEVICE, device.toMap()); + dst.putExtra(DEVICEDETAILS_PARAM_DEVICE_ID, deviceIds.get(device)); + startActivity(dst); + } + + public String addDevice(com.example.networker.ui.viewmodel.DummyDevice device) { + DummyDevice deviceModel = device.toModel(); + + String deviceId = dbBackend.makeDeviceId(dbCurrentUser.getId()); + dbBackend.setUserDevice(dbCurrentUser.getId(), deviceId, deviceModel); + return deviceId; + } + + public void populateDeviceList() { + //dbBackend.setUserDevice(dbCurrentUser.getId(), dbBackend.makeDeviceId(dbCurrentUser.getId()), new DummyDevice("akarmi")); + if (dbCurrentUser != null && deviceChooserAdapter != null) { + String userId = dbCurrentUser.getId(); + dbBackend.fetchAllUserDevicesRealtime(userId).subscribe( + listOfPairs -> { + com.example.networker.ui.viewmodel.DummyDevice device; + deviceChooserAdapter.clear(); + + for (Pair devicePair : listOfPairs) { + device = com.example.networker.ui.viewmodel.DummyDevice.fromModel(devicePair.second); + + deviceIds.put(device, devicePair.first); + deviceChooserAdapter.addDevice(device); + } + } + ); + } + } + + public static DocumentReference getDbCurrentUser() { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if (user == null) { + return null; + } + return new DummyDeviceBackend().getUserBase(user.getUid()); + } + public static String getDeviceId(Device device) { + return deviceIds.getOrDefault(device, null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/networker/database/domain/DummyDeviceBackend.java b/app/src/main/java/com/example/networker/database/domain/DummyDeviceBackend.java new file mode 100644 index 0000000..c1fc6cb --- /dev/null +++ b/app/src/main/java/com/example/networker/database/domain/DummyDeviceBackend.java @@ -0,0 +1,166 @@ +package com.example.networker.database.domain; + +import android.util.Log; +import android.util.Pair; + +import com.example.networker.database.model.device.DeviceInterface; +import com.example.networker.database.model.device.DummyDevice; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.ReplaySubject; + +public class DummyDeviceBackend { + private static final String LOG_TAG = DummyDeviceBackend.class.getName(); + + public FirebaseFirestore getDatabaseRoot() { + return FirebaseFirestore.getInstance(); + } + + public CollectionReference getUserList() { + return getDatabaseRoot().collection("users"); + } + public DocumentReference getUserBase(String userId) { + return getUserList().document(userId); + } + + public CollectionReference getUserDevicesListBase(String userId) { + return getUserBase(userId).collection("devices"); + } + + public DocumentReference getUserDeviceBase(String userId, String deviceId) { + return getUserDevicesListBase(userId).document(deviceId); + } + + public Observable> getUserDevice(String userId, String deviceId) { + DocumentReference reference = getUserDeviceBase(userId, deviceId); + ReplaySubject> obs = ReplaySubject.create(); + + reference.get().addOnSuccessListener(documentSnapshot -> { + + Map data = documentSnapshot.getData(); + Log.i(LOG_TAG, "5" + String.valueOf(data)); + if (data != null) { + Object data_settings = data.getOrDefault("settings", null); + Log.i(LOG_TAG, "6" + String.valueOf(data_settings)); + if (data_settings != null && data_settings instanceof Map) { + Object interfaceList = ((Map) data_settings).get("interfaceList"); + Log.i(LOG_TAG, "7" + String.valueOf(interfaceList)); + if (interfaceList != null && interfaceList instanceof List) { + ((List)interfaceList).forEach(devIf -> Log.i(LOG_TAG, "8" + String.valueOf(devIf))); + + //((List)interfaceList).forEach((k, devIf) -> Log.i(LOG_TAG, "4" + String.valueOf(devIf))); + } + } + } + + DummyDevice device = documentSnapshot.toObject(DummyDevice.class); + + Log.i(LOG_TAG, "9" + String.valueOf(device.getSettings().getInterfaceList())); + device.getSettings().getInterfaceList().forEach(devIf -> Log.i(LOG_TAG, "10" + String.valueOf(devIf.getAddress()))); + + if (!documentSnapshot.exists() || device == null) { + obs.onError(new RuntimeException("Device not exists or cannot be loaded")); + return; + } + obs.onNext(new Pair<>(documentSnapshot.getId(), device)); + obs.onComplete(); + }); + + return obs; + } + + public Observable> fetchAllUserDevices(String userId) { + CollectionReference reference = getUserDevicesListBase(userId); + ReplaySubject> obs = ReplaySubject.create(); + + reference.get().addOnSuccessListener(documentSnapshots -> { + for (DocumentSnapshot snapshot: documentSnapshots.getDocuments()) { + if (snapshot != null && snapshot.exists()) { + DummyDevice device = snapshot.toObject(DummyDevice.class); + if (device != null) { + obs.onNext(new Pair<>(snapshot.getId(), device)); + } + } + } + obs.onComplete(); + }); + + return obs; + } + + public Observable>> fetchAllUserDevicesRealtime(String userId) { + CollectionReference reference = getUserDevicesListBase(userId); + ReplaySubject>> obs = ReplaySubject.create(); + + reference.addSnapshotListener( + (value, error) -> { + if (error == null && !value.isEmpty()) { + List> result = new LinkedList<>(); + for (DocumentSnapshot snapshot: value.getDocuments()) { + if (snapshot != null && snapshot.exists()) { + result.add(new Pair<>(snapshot.getId(), snapshot.toObject(DummyDevice.class))); + } + } + obs.onNext(result); + } + } + ); + + return obs; + } + + public String makeDeviceId(String userId) { + return getUserDevicesListBase(userId).document().getId(); + } + + public Task addUserDevice(String userId, DummyDevice device) { + return getUserDevicesListBase(userId).document().set(device); + } + + public void addInterface(String userId, String deviceId, DeviceInterface deviceInterface) { + getUserDevice(userId, deviceId).subscribe( + device -> { + DummyDevice deviceInst = device.second; + deviceInst.getSettings().getInterfaceList().add(deviceInterface); + setUserDevice(userId, deviceId, deviceInst); + } + ); + } + + public Task setUserDevice(String userId, String deviceId, DummyDevice device) { + return getUserDevicesListBase(userId).document(deviceId).set(device); + } + + public void setInterface(String userId, String deviceId, int interfaceNo, DeviceInterface deviceInterface) { + getUserDevice(userId, deviceId).subscribe( + device -> { + DummyDevice deviceInst = device.second; + deviceInst.getSettings().getInterfaceList().set(interfaceNo, deviceInterface); + setUserDevice(userId, deviceId, deviceInst); + } + ); + } + + public Task deleteUserDevice(String userId, String deviceId) { + return getUserDevicesListBase(userId).document(deviceId).delete(); + } + + public void deleteInterface(String userId, String deviceId, int interfaceNo) { + getUserDevice(userId, deviceId).subscribe( + device -> { + DummyDevice deviceInst = device.second; + deviceInst.getSettings().getInterfaceList().remove(interfaceNo); + setUserDevice(userId, deviceId, deviceInst); + } + ); + } +} diff --git a/app/src/main/java/com/example/networker/database/domain/FirebaseConnector.java b/app/src/main/java/com/example/networker/database/domain/FirebaseConnector.java new file mode 100644 index 0000000..dbd3bf1 --- /dev/null +++ b/app/src/main/java/com/example/networker/database/domain/FirebaseConnector.java @@ -0,0 +1,15 @@ +package com.example.networker.database.domain; + +import com.google.firebase.database.FirebaseDatabase; + +public class FirebaseConnector { + private FirebaseDatabase firebaseDatabase; + + public FirebaseConnector() { + firebaseDatabase = FirebaseDatabase.getInstance(); + } + + public FirebaseDatabase getDBInstance() { + return firebaseDatabase; + } +} diff --git a/app/src/main/java/com/example/networker/database/model/device/DeviceInterface.java b/app/src/main/java/com/example/networker/database/model/device/DeviceInterface.java new file mode 100644 index 0000000..c26655a --- /dev/null +++ b/app/src/main/java/com/example/networker/database/model/device/DeviceInterface.java @@ -0,0 +1,48 @@ +package com.example.networker.database.model.device; + +import java.net.Inet4Address; +import java.net.UnknownHostException; + +public class DeviceInterface { + public enum Type { + WAN, LAN + } + private String address = null; + private int maskLength; + private Type interfaceType = Type.WAN; + + public DeviceInterface() { } + + public DeviceInterface(String address, int maskLength, Type interfaceType) { + if (address == null) { + throw new IllegalArgumentException("address cannot be null"); + } + this.address = address; + this.maskLength = maskLength; + this.interfaceType = interfaceType; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getMaskLength() { + return maskLength; + } + + public void setMaskLength(int maskLength) { + this.maskLength = maskLength; + } + + public Type getInterfaceType() { + return interfaceType; + } + + public void setInterfaceType(Type interfaceType) { + this.interfaceType = interfaceType; + } +} diff --git a/app/src/main/java/com/example/networker/database/model/device/DummyDevice.java b/app/src/main/java/com/example/networker/database/model/device/DummyDevice.java new file mode 100644 index 0000000..a36f00c --- /dev/null +++ b/app/src/main/java/com/example/networker/database/model/device/DummyDevice.java @@ -0,0 +1,29 @@ +package com.example.networker.database.model.device; + +public class DummyDevice { + private String name; + private DummyDeviceSettings settings = new DummyDeviceSettings(); + + public DummyDevice() { + } + + public DummyDevice(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DummyDeviceSettings getSettings() { + return settings; + } + + public void setSettings(DummyDeviceSettings settings) { + this.settings = settings; + } +} diff --git a/app/src/main/java/com/example/networker/database/model/device/DummyDeviceSettings.java b/app/src/main/java/com/example/networker/database/model/device/DummyDeviceSettings.java new file mode 100644 index 0000000..56b7042 --- /dev/null +++ b/app/src/main/java/com/example/networker/database/model/device/DummyDeviceSettings.java @@ -0,0 +1,27 @@ +package com.example.networker.database.model.device; + +import com.example.networker.database.model.device.DeviceInterface; + +import java.util.ArrayList; +import java.util.List; + +public class DummyDeviceSettings { + private List interfaceList = new ArrayList<>(); + private List portForwards = new ArrayList<>(); + + public List getInterfaceList() { + return interfaceList; + } + + public void setInterfaceList(List interfaceList) { + this.interfaceList = interfaceList; + } + + public List getPortForwards() { + return portForwards; + } + + public void setPortForwards(List portForwards) { + this.portForwards = portForwards; + } +} diff --git a/app/src/main/java/com/example/networker/database/model/device/PortForward.java b/app/src/main/java/com/example/networker/database/model/device/PortForward.java new file mode 100644 index 0000000..310dbeb --- /dev/null +++ b/app/src/main/java/com/example/networker/database/model/device/PortForward.java @@ -0,0 +1,51 @@ +package com.example.networker.database.model.device; + +import java.net.Inet4Address; + +public class PortForward { + private int wanPort; + private Inet4Address lanHost; + private int lanHostPort; + private boolean hairpinNAT; + + public PortForward() { } + + public PortForward(int wanPort, Inet4Address lanHost, int lanHostPort, boolean hairpinNAT) { + this.wanPort = wanPort; + this.lanHost = lanHost; + this.lanHostPort = lanHostPort; + this.hairpinNAT = hairpinNAT; + } + + public int getWanPort() { + return wanPort; + } + + public void setWanPort(int wanPort) { + this.wanPort = wanPort; + } + + public Inet4Address getLanHost() { + return lanHost; + } + + public void setLanHost(Inet4Address lanHost) { + this.lanHost = lanHost; + } + + public int getLanHostPort() { + return lanHostPort; + } + + public void setLanHostPort(int lanHostPort) { + this.lanHostPort = lanHostPort; + } + + public boolean isHairpinNAT() { + return hairpinNAT; + } + + public void setHairpinNAT(boolean hairpinNAT) { + this.hairpinNAT = hairpinNAT; + } +} diff --git a/app/src/main/java/com/example/networker/ui/adapter/DeviceChooser.java b/app/src/main/java/com/example/networker/ui/adapter/DeviceChooser.java new file mode 100644 index 0000000..b066fc7 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/adapter/DeviceChooser.java @@ -0,0 +1,122 @@ +package com.example.networker.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.R; +import com.example.networker.ui.viewmodel.Device; + +import java.util.ArrayList; + +// TODO: mit nem adtam hozzá a gyakorlati videóból: +// - filter +// - slide animáció +public class DeviceChooser extends RecyclerView.Adapter { + private final ArrayList deviceList = new ArrayList<>(); + private ArrayList filteredDeviceList = deviceList; + private final Context context; + + private final RecyclerView deviceSelectorList; + + public DeviceChooser(Context context, RecyclerView deviceSelectorList) { + this.context = context; + this.deviceSelectorList = deviceSelectorList; + + deviceSelectorList.setAdapter(this); + } + + public RecyclerView getDeviceSelectorList() { + return deviceSelectorList; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private CheckBox deviceConnected; + private TextView deviceName; + private TextView connectorName; + private TextView locationUri; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + + deviceConnected = itemView.findViewById(R.id.deviceConnected); + deviceName = itemView.findViewById(R.id.deviceName); + connectorName = itemView.findViewById(R.id.connectorType); + locationUri = itemView.findViewById(R.id.locationUri); + } + + public void bindTo(Device device) { + deviceConnected.setChecked(Boolean.TRUE.equals(device.isConnected())); + deviceName.setText(device.getName()); + connectorName.setText(device.getVendor()); + locationUri.setText(device.getLocationUri()); + + // TODO: kattintható CardView elem + hozzá handler + } + + public CheckBox getDeviceConnectedInput() { + return deviceConnected; + } + + public TextView getDeviceNameInput() { + return deviceName; + } + + public TextView getConnectorNameInput() { + return connectorName; + } + + public TextView getLocationUriInput() { + return locationUri; + } + } + + public ArrayList getFilteredDeviceList() { + return filteredDeviceList; + } + + public void addDevice(Device device) { + deviceList.add(device); + this.notifyDataSetChanged(); + } + public void addDevice(Iterable device) { + device.forEach(deviceList::add); + this.notifyDataSetChanged(); + } + + public void deleteDevice(Device device) { + deviceList.remove(device); + this.notifyDataSetChanged(); + } + + public void clear() { + deviceList.clear(); + this.notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.listitem_device, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Device device = filteredDeviceList.get(position); + + holder.bindTo(device); + } + + @Override + public int getItemCount() { + return filteredDeviceList.size(); + } + + +} diff --git a/app/src/main/java/com/example/networker/ui/adapter/DeviceInterfacesList.java b/app/src/main/java/com/example/networker/ui/adapter/DeviceInterfacesList.java new file mode 100644 index 0000000..103a029 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/adapter/DeviceInterfacesList.java @@ -0,0 +1,99 @@ +package com.example.networker.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.R; +import com.example.networker.database.model.device.DeviceInterface; + +import java.util.ArrayList; + +public class DeviceInterfacesList extends RecyclerView.Adapter { + private final ArrayList interfaceList = new ArrayList<>(); + private ArrayList filteredInterfaceList = interfaceList; + private final Context context; + + private final RecyclerView interfaceSelectorList; + + public DeviceInterfacesList(Context context, RecyclerView interfaceSelectorList) { + this.context = context; + this.interfaceSelectorList = interfaceSelectorList; + + interfaceSelectorList.setAdapter(this); + } + + public RecyclerView getInterfaceSelectorList() { + return interfaceSelectorList; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private TextView ifType; + private TextView address; + private TextView netmask; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + + ifType = itemView.findViewById(R.id.interface_type); + address = itemView.findViewById(R.id.address); + netmask = itemView.findViewById(R.id.netmask); + } + + public void bindTo(DeviceInterface deviceInterface) { + ifType.setText(deviceInterface.getInterfaceType().toString()); + address.setText(deviceInterface.getAddress()); + netmask.setText(String.valueOf(deviceInterface.getMaskLength())); + } + + public TextView getIfType() { + return ifType; + } + + public TextView getAddress() { + return address; + } + + public TextView getNetmask() { + return netmask; + } + } + + public ArrayList getFilteredInterfaceList() { + return filteredInterfaceList; + } + + public void addInterface(DeviceInterface device) { + interfaceList.add(device); + this.notifyDataSetChanged(); + } + public void deleteInterface(int position) { + interfaceList.remove(position); + this.notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.listitem_interface, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + DeviceInterface deviceInterface = filteredInterfaceList.get(position); + + holder.bindTo(deviceInterface); + } + + @Override + public int getItemCount() { + return filteredInterfaceList.size(); + } + + +} diff --git a/app/src/main/java/com/example/networker/ui/adapter/MetricGraphList.java b/app/src/main/java/com/example/networker/ui/adapter/MetricGraphList.java new file mode 100644 index 0000000..7667e80 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/adapter/MetricGraphList.java @@ -0,0 +1,73 @@ +package com.example.networker.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.R; +import com.example.networker.ui.adapter.viewholder.MetricGraphItem; +import com.example.networker.ui.viewmodel.MetricHistory; + +import java.util.ArrayList; + +// TODO: mit nem adtam hozzá a gyakorlati videóból: +// - filter +// - slide animáció +public class MetricGraphList extends RecyclerView.Adapter> { + private final ArrayList> metricList = new ArrayList<>(); + private ArrayList> filteredMetricList = metricList; + private final Context context; + + private final RecyclerView metricSelectorList; + private int graphWindowSize; + + public MetricGraphList(Context context, RecyclerView metricSelectorList, int graphDefaultWindow) { + this.context = context; + this.metricSelectorList = metricSelectorList; + this.graphWindowSize = graphDefaultWindow; + + metricSelectorList.setAdapter(this); + } + + public Context getContext() { + return context; + } + + public int getGraphWindowSize() { + return graphWindowSize; + } + + public void setGraphWindowSize(int graphWindowSize) { + this.graphWindowSize = graphWindowSize; + } + + public void addMetric(MetricHistory metricHistory) { + metricList.add(metricHistory); + this.notifyDataSetChanged(); + } + + protected MetricHistory getHolderItem(int position) { + return filteredMetricList.get(position); + } + + @NonNull + @Override + public MetricGraphItem onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new MetricGraphItem<>(LayoutInflater.from(context).inflate(R.layout.listitem_metric_graph, parent, false), graphWindowSize); + } + + @Override + public void onBindViewHolder(@NonNull MetricGraphItem holder, int position) { + holder.bindTo(getHolderItem(position)); + } + + @Override + public int getItemCount() { + return filteredMetricList.size(); + } + + +} diff --git a/app/src/main/java/com/example/networker/ui/adapter/viewholder/MetricGraphItem.java b/app/src/main/java/com/example/networker/ui/adapter/viewholder/MetricGraphItem.java new file mode 100644 index 0000000..1954114 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/adapter/viewholder/MetricGraphItem.java @@ -0,0 +1,81 @@ +package com.example.networker.ui.adapter.viewholder; + +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.networker.MainActivity; +import com.example.networker.R; +import com.example.networker.ui.viewmodel.LiveMetricHistory; +import com.example.networker.ui.viewmodel.MetricHistory; +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import io.reactivex.rxjava3.disposables.Disposable; + +public class MetricGraphItem extends RecyclerView.ViewHolder { + private static final String LOG_TAG = MetricGraphItem.class.getName(); + private GraphView graph; + private LineGraphSeries graphSeries = new LineGraphSeries<>(); + private int graphInputSize = 0; + private int windowSize; + private Disposable metricListener; + + public MetricGraphItem(@NonNull View itemView, int windowSize) { + super(itemView); + + graph = itemView.findViewById(R.id.graph); + this.windowSize = windowSize; + graph.addSeries(graphSeries); + } + + protected GraphView getGraph() { + return graph; + } + + protected LineGraphSeries getGraphSeries() { + return graphSeries; + } + + public int getGraphInputSize() { + return graphInputSize; + } + + public int getWindowSize() { + return windowSize; + } + + protected void setWindowSize(int windowSize) { + this.windowSize = windowSize; + } + + public void bindTo(MetricHistory metricHistory) { + if (metricListener != null && !metricListener.isDisposed()) { + metricListener.dispose(); + } + + graphInputSize = 0; + DataPoint[] graphData = new DataPoint[metricHistory.size()]; + for (V data : metricHistory.getMetricData()) { + graphData[graphInputSize] = new DataPoint(graphInputSize, (double)data); + graphInputSize++; + } + graphSeries.resetData(graphData); + + if (metricHistory instanceof LiveMetricHistory) { + metricListener = ((LiveMetricHistory) metricHistory).listenUpdates().subscribe(this::appendData); + } + + // TODO: kattintható CardView elem + hozzá handler + } + + public void appendData(V metric) { + //Log.i(LOG_TAG, "Metric: " + String.valueOf(windowSize) + " " + String.valueOf(graphInputSize) + " " + String.valueOf(metric)); + + graphSeries.appendData(new DataPoint((double) graphInputSize, (double)metric), true, windowSize); + graphInputSize++; + } +} diff --git a/app/src/main/java/com/example/networker/ui/viewmodel/Device.java b/app/src/main/java/com/example/networker/ui/viewmodel/Device.java new file mode 100644 index 0000000..23041fc --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/viewmodel/Device.java @@ -0,0 +1,83 @@ +package com.example.networker.ui.viewmodel; + +import android.os.Bundle; +import android.os.PersistableBundle; + +import java.util.HashMap; +import java.util.Map; + +import io.reactivex.rxjava3.annotations.Nullable; + +public abstract class Device { + private String vendor; + private final String locationUri; + private @Nullable Boolean connected = null; + private @Nullable Boolean commandInitiated = null; + private @Nullable String name = null; + private Map properties = new HashMap<>(); + private Map mainFlowSettings = new HashMap<>(); + + public Device(String locationUri) { + this(locationUri, null); + } + + public Device(String locationUri, String name) { + if (locationUri == null) { + throw new IllegalArgumentException("locationUri cannot be null"); + } + + this.locationUri = locationUri; + this.name = name; + } + + public static Device fromMap(Map dataMap) { + throw new RuntimeException("not implemented"); + } + + public abstract Map toMap(); + + + public String getVendor() { + return vendor; + } + + public void setVendor(String customVendor) { + if (customVendor == null) { + throw new IllegalArgumentException("customVendor cannot be null"); + } + this.vendor = customVendor; + } + + public String getLocationUri() { + return locationUri; + } + + public @Nullable Boolean isConnected() { + return connected; + } + + public void setConnected(Boolean connected) { + this.connected = connected; + } + + public @Nullable Boolean isCommandInitiated() { + return commandInitiated; + } + + public void setCommandInitiated(Boolean commandInitiated) { + this.commandInitiated = commandInitiated; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getProperties() { + return properties; + } + +} diff --git a/app/src/main/java/com/example/networker/ui/viewmodel/DeviceInterface.java b/app/src/main/java/com/example/networker/ui/viewmodel/DeviceInterface.java new file mode 100644 index 0000000..6a1df15 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/viewmodel/DeviceInterface.java @@ -0,0 +1,36 @@ +package com.example.networker.ui.viewmodel; + +import java.util.HashMap; +import java.util.Map; + +public class DeviceInterface extends com.example.networker.database.model.device.DeviceInterface { + public DeviceInterface() { } + + public DeviceInterface(String address, int maskLength, Type interfaceType) { + super(address, maskLength, interfaceType); + } + + public HashMap toMap() { + HashMap dataMap = new HashMap<>(); + + dataMap.put("type", String.valueOf(getInterfaceType())); + dataMap.put("address", String.valueOf(getAddress())); + dataMap.put("mask", String.valueOf(getMaskLength())); + return dataMap; + } + + public static DeviceInterface fromMap(Map dataMap) { + if (dataMap == null) { + throw new IllegalArgumentException("dataMap cannot be null"); + } + String ifType = dataMap.get("type"); + String address = dataMap.get("address"); + String mask = dataMap.get("mask"); + if (ifType == null || address == null || mask == null) { + throw new IllegalArgumentException("incompatible input dataMap"); + } + + DeviceInterface device = new DeviceInterface(address, Integer.parseInt(mask), Type.valueOf(ifType)); + return device; + } +} diff --git a/app/src/main/java/com/example/networker/ui/viewmodel/DummyDevice.java b/app/src/main/java/com/example/networker/ui/viewmodel/DummyDevice.java new file mode 100644 index 0000000..80fd2e1 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/viewmodel/DummyDevice.java @@ -0,0 +1,68 @@ +package com.example.networker.ui.viewmodel; + +import com.example.networker.database.model.device.DummyDeviceSettings; + +import java.util.HashMap; +import java.util.Map; + +public class DummyDevice extends Device { + private DummyDeviceSettings settings; + + public DummyDevice() { + this(null); + } + + public DummyDevice(String name) { + super("dummy://" + (name != null ? "<"+name+">/" : "ENOENT/"), name); + setVendor("DummyDevices"); + + settings = new DummyDeviceSettings(); + } + + @Override + public HashMap toMap() { + HashMap dataMap = new HashMap<>(); + + dataMap.put("name", String.valueOf(getName())); + return dataMap; + } + + public static DummyDevice fromMap(Map dataMap) { + if (dataMap == null) { + throw new IllegalArgumentException("dataMap cannot be null"); + } + String name = dataMap.get("name"); + if (name == null) { + throw new IllegalArgumentException("incompatible input dataMap"); + } + + DummyDevice device = new DummyDevice(name); + return device; + } + + public DummyDeviceSettings getSettings() { + return settings; + } + + public void setSettings(DummyDeviceSettings settings) { + this.settings = settings; + } + + public static DummyDevice fromModel(com.example.networker.database.model.device.DummyDevice device) { + String name = device.getName(); + if (name == null || name.isEmpty()) { + name = "Untitled"; + } + DummyDevice viewmodelDevice = new DummyDevice(name); + + viewmodelDevice.setSettings(device.getSettings()); + return viewmodelDevice; + } + + public com.example.networker.database.model.device.DummyDevice toModel() { + com.example.networker.database.model.device.DummyDevice device = new com.example.networker.database.model.device.DummyDevice(getName()); + + device.setSettings(getSettings()); + return device; + } +} diff --git a/app/src/main/java/com/example/networker/ui/viewmodel/LiveMetricHistory.java b/app/src/main/java/com/example/networker/ui/viewmodel/LiveMetricHistory.java new file mode 100644 index 0000000..67b5508 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/viewmodel/LiveMetricHistory.java @@ -0,0 +1,42 @@ +package com.example.networker.ui.viewmodel; + +import java.util.LinkedList; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.PublishSubject; + +public class LiveMetricHistory extends MetricHistory { + private final PublishSubject observable = PublishSubject.create(); + public LiveMetricHistory(String name) { + super(name); + } + + public LiveMetricHistory(String name, Iterable metricData) { + super(name, metricData); + } + + public Observable listenUpdates() { + return observable; + } + + @Override + public void appendData(Iterable metricData) { + LinkedList dataList = new LinkedList<>(); + for (V metric : metricData) { + if (metric != null && !observable.hasComplete() && !observable.hasThrowable()) { + observable.onNext(metric); + } + dataList.add(metric); + } + + super.appendData(dataList); + } + + @Override + public void appendData(V data) { + if (data != null && !observable.hasComplete() && !observable.hasThrowable()) { + observable.onNext(data); + } + super.appendData(data); + } +} diff --git a/app/src/main/java/com/example/networker/ui/viewmodel/MetricHistory.java b/app/src/main/java/com/example/networker/ui/viewmodel/MetricHistory.java new file mode 100644 index 0000000..57a3405 --- /dev/null +++ b/app/src/main/java/com/example/networker/ui/viewmodel/MetricHistory.java @@ -0,0 +1,64 @@ +package com.example.networker.ui.viewmodel; + +import java.util.LinkedList; +import java.util.List; + +// TODO: queue/buffer? +//public class MetricHistory { +public class MetricHistory { + private String name; + private LinkedList metricData = new LinkedList<>(); + //private @Nullable Iterable dataSamplingIndices; + + public MetricHistory(String name) { + this.name = name; + } + + //public MetricHistory(String title, Iterable metricData, Iterable dataSamplingIndices) { + public MetricHistory(String name, Iterable metricData) { + if (name == null) { + throw new IllegalArgumentException("title cannot be null"); + } + + this.name = name; + if (metricData != null) { + metricData.forEach(this.metricData::add); + } + //this.dataSamplingIndices = dataSamplingIndices; + } + + public String getName() { + return name; + } + + public Iterable getMetricData() { + return metricData; + } + + public int size() { + return metricData.size(); + } + + //public @Nullable Iterable getDataSamplingIndices() { + // return dataSamplingIndices; + //} + + //public void appendData(Iterable metricData, Iterable dataSamplingIndices) { + public void appendData(Iterable metricData) { + if (metricData == null) { + throw new IllegalArgumentException("metricData cannot be null"); + } + + metricData.forEach(this.metricData::add); + //this.dataSamplingIndices = dataSamplingIndices; + } + + public void appendData(V data) { + if (data == null) { + throw new IllegalArgumentException("data cannot be null"); + } + + this.metricData.add(data); + //this.dataSamplingIndices = dataSamplingIndices; + } +} diff --git a/app/src/main/res/drawable/ic_add_list_item.xml b/app/src/main/res/drawable/ic_add_list_item.xml new file mode 100644 index 0000000..89633bb --- /dev/null +++ b/app/src/main/res/drawable/ic_add_list_item.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_devices.xml b/app/src/main/res/drawable/ic_devices.xml new file mode 100644 index 0000000..fcc941f --- /dev/null +++ b/app/src/main/res/drawable/ic_devices.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_flows.xml b/app/src/main/res/drawable/ic_flows.xml new file mode 100644 index 0000000..4fa69dc --- /dev/null +++ b/app/src/main/res/drawable/ic_flows.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_login.xml b/app/src/main/res/drawable/ic_menu_login.xml new file mode 100644 index 0000000..93ce250 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_login.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_device_details.xml b/app/src/main/res/layout/activity_device_details.xml new file mode 100644 index 0000000..2fccccc --- /dev/null +++ b/app/src/main/res/layout/activity_device_details.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..d6670c4 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + +