diff --git a/app/build.gradle b/app/build.gradle
index ce0e08a..6ddd215 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,8 @@
apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.firebase.crashlytics'
+apply plugin: 'androidx.navigation.safeargs'
android {
compileSdkVersion 33
@@ -7,10 +10,16 @@ android {
applicationId "com.nagpal.shivam.vtucslab"
minSdkVersion 19
targetSdkVersion 33
- versionCode 8
- versionName "6.2"
+ versionCode 9
+ versionName "7.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
+ }
+ }
}
buildTypes {
release {
@@ -18,35 +27,63 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ kotlin {
+ jvmToolchain(11)
+ }
+
+ android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = 11
+ }
+ }
buildFeatures {
- dataBinding true
+ viewBinding true
}
namespace 'com.nagpal.shivam.vtucslab'
}
dependencies {
+ implementation platform("com.google.firebase:firebase-bom:$versions.firebase_bom")
implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.google.android.material:material:1.8.0'
- implementation "androidx.multidex:multidex:2.0.1"
+ implementation "androidx.core:core-ktx:$versions.core_ktx"
+ implementation "androidx.appcompat:appcompat:$versions.appcompat"
+ implementation "androidx.constraintlayout:constraintlayout:$versions.constraintlayout"
+ implementation "com.google.android.material:material:$versions.material"
+ implementation "androidx.multidex:multidex:$versions.multidex"
-// GSON
- implementation 'com.google.code.gson:gson:2.8.9'
+ // Navigation
+ implementation "androidx.navigation:navigation-fragment-ktx:$versions.navigation"
+ implementation "androidx.navigation:navigation-ui-ktx:$versions.navigation"
-// Firebase SDK
- implementation('com.google.firebase:firebase-core:21.1.1') {
- exclude module: 'support-v4'
- }
- implementation('com.google.firebase:firebase-messaging:23.1.2') {
- exclude module: 'support-v4'
- }
- implementation 'com.google.firebase:firebase-crashlytics:18.3.5'
+ implementation "androidx.room:room-runtime:$versions.room"
+ kapt "androidx.room:room-compiler:$versions.room"
+
+ // https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-kotlin
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson_module_kotlin"
+
+ // https://mvnrepository.com/artifact/androidx.swiperefreshlayout/swiperefreshlayout
+ implementation "androidx.swiperefreshlayout:swiperefreshlayout:$versions.swipe_refresh_layout"
+
+ // Firebase SDK
+ implementation 'com.google.firebase:firebase-messaging-ktx'
+ implementation 'com.google.firebase:firebase-crashlytics-ktx'
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
+ implementation "com.squareup.retrofit2:converter-jackson:$versions.retrofit"
+ implementation "com.squareup.retrofit2:converter-scalars:$versions.retrofit"
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test:runner:1.5.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ testImplementation "junit:junit:$versions.junit"
+ androidTestImplementation "androidx.test:runner:$versions.test_runner"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espresso_core"
}
apply plugin: 'com.google.gms.google-services'
+apply plugin: 'org.jetbrains.kotlin.android'
diff --git a/app/schemas/com.nagpal.shivam.vtucslab.data.local.AppDatabase/1.json b/app/schemas/com.nagpal.shivam.vtucslab.data.local.AppDatabase/1.json
new file mode 100644
index 0000000..ddc9153
--- /dev/null
+++ b/app/schemas/com.nagpal.shivam.vtucslab.data.local.AppDatabase/1.json
@@ -0,0 +1,52 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "f78e881b1af6632ac37cd5f3188a2360",
+ "entities": [
+ {
+ "tableName": "lab_response",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `response` TEXT NOT NULL, `response_type` TEXT NOT NULL, `fetched_at` INTEGER NOT NULL, PRIMARY KEY(`url`))",
+ "fields": [
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "response",
+ "columnName": "response",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "responseType",
+ "columnName": "response_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fetchedAt",
+ "columnName": "fetched_at",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "url"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f78e881b1af6632ac37cd5f3188a2360')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6186b9e..fb387c3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,7 @@
-
-
-
+ android:name=".activities.MainActivity"
+ android:exported="true"
+ android:theme="@style/AppTheme">
-
-
-
-
-
-
-
-
-
+
-
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/DisplayActivity.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/DisplayActivity.java
deleted file mode 100644
index c3abfa0..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/DisplayActivity.java
+++ /dev/null
@@ -1,250 +0,0 @@
-package com.nagpal.shivam.vtucslab.Activity;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.NavUtils;
-import androidx.databinding.DataBindingUtil;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-
-import com.nagpal.shivam.vtucslab.Loader.RawStreamLoader;
-import com.nagpal.shivam.vtucslab.R;
-import com.nagpal.shivam.vtucslab.Utility.ConstantVariables;
-import com.nagpal.shivam.vtucslab.databinding.ActivityDisplayBinding;
-
-public class DisplayActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
-
- private static final int LOADER_ID = 1;
- private static final String SUCCEEDED_KEY = "succeeded_key";
- private static final String SCROLL_X_KEY = "scroll_x_key";
- private static final String SCROLL_Y_KEY = "scroll_y_key";
-
- private static final String LOG_TAG = DisplayActivity.class.getSimpleName();
-
-
- // private TextView displayTextView;
-// private PageView mDisplayPageView;
-// private TextView mEmptyTextView;
-// private ProgressBar mProgressBar;
-
- private Boolean mSucceeded = false;
- private String mUrl;
- private String mTitle;
- private String mCode;
-
-
- private ActivityDisplayBinding mBinding;
- private int mScrollX;
- private int mScrollY;
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu_display_activity, menu);
- if (mSucceeded) {
- MenuItem copyMenuItem = menu.findItem(R.id.menu_item_copy_display_activity);
- copyMenuItem.setEnabled(true);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(DisplayActivity.this);
- return true;
-
- case R.id.display_menu_item_refresh:
- recreate();
- return true;
-
- case R.id.menu_item_copy_display_activity:
- ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
- ClipData clipData = new ClipData(ClipData.newPlainText("Code", mCode));
- if (clipboardManager != null) {
- clipboardManager.setPrimaryClip(clipData);
- Toast.makeText(DisplayActivity.this, "Code copied to Clipboard.", Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding = DataBindingUtil.setContentView(this, R.layout.activity_display);
-// setContentView(R.layout.activity_display);
-// logd("OnCreate Called");
- ActionBar actionBar = getSupportActionBar();
-
- initViews();
-
- if (savedInstanceState != null) {
- mSucceeded = savedInstanceState.getBoolean(SUCCEEDED_KEY, false);
- mScrollX = savedInstanceState.getInt(SCROLL_X_KEY, 0);
- mScrollY = savedInstanceState.getInt(SCROLL_Y_KEY, 0);
- }
-
- Intent intent = getIntent();
- mTitle = intent.getStringExtra(ConstantVariables.title_intent_tag);
- mUrl = intent.getStringExtra(ConstantVariables.url_intent_tag);
-
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
-
- DisplayActivity.this.setTitle(mTitle);
-
- ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connectivityManager != null ? connectivityManager.getActiveNetworkInfo() : null;
- LoaderManager loaderManager = LoaderManager.getInstance(this);
-
- if (!mSucceeded) {
- loaderManager.destroyLoader(LOADER_ID);
- }
-
- if (networkInfo != null && networkInfo.isConnected()) {
- loaderManager.initLoader(LOADER_ID, null, DisplayActivity.this);
- } else {
-// mProgressBar.setVisibility(View.GONE);
- mBinding.progressBarDisplay.setVisibility(View.GONE);
-// mEmptyTextView.setVisibility(View.VISIBLE);
- mBinding.emptyTextViewDisplay.setVisibility(View.VISIBLE);
-// mEmptyTextView.setText(R.string.no_internet_connection);
- mBinding.emptyTextViewDisplay.setText(R.string.no_internet_connection);
- mSucceeded = false;
- }
- }
-
- private void initViews() {
-// mDisplayPageView = findViewById(R.id.display_page_view);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-// mDisplayPageView.setLetterSpacing(0.1f);
- mBinding.displayTextView.setLetterSpacing(0.1f);
- }
-// mEmptyTextView = findViewById(R.id.empty_text_view_display);
-// mProgressBar = findViewById(R.id.progress_bar_display);
-
- //setupScrolling();
- }
-
-// private void setupScrolling() {
-// mBinding.horizontalScroll.setOnTouchListener(new View.OnTouchListener() {
-// @Override
-// public boolean onTouch(View v, MotionEvent event) {
-// return false;
-// }
-// });
-// mBinding.verticalScroll.setOnTouchListener(new View.OnTouchListener() {
-// @Override
-// public boolean onTouch(View v, MotionEvent event) {
-// return false;
-// }
-// });
-// mBinding.displayTextView.setOnTouchListener(new View.OnTouchListener() {
-// private float prevX, prevY, curX, curY;
-// private boolean handled = false;
-//
-// @Override
-// public boolean onTouch(View v, MotionEvent event) {
-// Log.v("Ontouch Called", "");
-// mBinding.verticalScroll.requestDisallowInterceptTouchEvent(true);
-// mBinding.horizontalScroll.requestDisallowInterceptTouchEvent(true);
-// switch (event.getAction()) {
-// case MotionEvent.ACTION_DOWN:
-// handled = false;
-// prevX = event.getX();
-// prevY = event.getY();
-// Log.v("Action Down", prevX + " " + prevY);
-// break;
-// case MotionEvent.ACTION_MOVE:
-// handled = true;
-// curX = event.getX();
-// curY = event.getY();
-// int dy = (int) (prevY - curY);
-// int dx = (int) (prevX - curX);
-//
-// prevX = curX;
-// prevY = curY;
-//
-//
-// Log.v("Action Move Cur", curX + " " + curY);
-//// Log.v("Action Move Delta", dx + " " + dy);
-//
-// mBinding.verticalScroll.scrollBy(0, dy);
-// mBinding.horizontalScroll.scrollBy(dx, 0);
-// break;
-// case MotionEvent.ACTION_UP:
-// Log.v("Action Up", event.getX() + " " + event.getY());
-// break;
-// }
-// return true;
-// }
-// });
-// }
-
- @Override
- public Loader onCreateLoader(int i, Bundle bundle) {
- return new RawStreamLoader(DisplayActivity.this, mUrl);
- }
-
- @Override
- public void onLoadFinished(Loader loader, String s) {
-// mProgressBar.setVisibility(View.GONE);
- mBinding.progressBarDisplay.setVisibility(View.GONE);
- if (TextUtils.isEmpty(s)) {
- Toast.makeText(DisplayActivity.this, getString(R.string.error_occurred), Toast.LENGTH_LONG).show();
- mSucceeded = false;
- return;
- }
-
- mSucceeded = true;
- mCode = s;
- s = s.replaceAll("\t", "\t\t");
-// displayTextView.setText(s);
-// mDisplayPageView.setText(s);
- mBinding.displayTextView.setText(s);
- new Handler(getMainLooper()).postDelayed(new Runnable() {
- @Override
- public void run() {
- mBinding.horizontalScroll.setScrollX(mScrollX);
- mBinding.verticalScroll.setScrollY(mScrollY);
- }
- }, 500);
- invalidateOptionsMenu();
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
-// displayTextView.setText(null);
-// mDisplayPageView.setText(null);
- mBinding.displayTextView.setText(null);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(SUCCEEDED_KEY, mSucceeded);
- outState.putInt(SCROLL_X_KEY, mBinding.horizontalScroll.getScrollX());
- outState.putInt(SCROLL_Y_KEY, mBinding.verticalScroll.getScrollY());
- super.onSaveInstanceState(outState);
- }
-
-// private void logd(String str) {
-// Log.d(LOG_TAG, str);
-// }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/ProgramActivity.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/ProgramActivity.java
deleted file mode 100644
index 8edb2af..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/ProgramActivity.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package com.nagpal.shivam.vtucslab.Activity;
-
-
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.NavUtils;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.nagpal.shivam.vtucslab.Adapter.ContentAdapter;
-import com.nagpal.shivam.vtucslab.Loader.InfoLoader;
-import com.nagpal.shivam.vtucslab.Model.ContentFile;
-import com.nagpal.shivam.vtucslab.Model.LabExperiment;
-import com.nagpal.shivam.vtucslab.Model.LabResponse;
-import com.nagpal.shivam.vtucslab.Model.Laboratory;
-import com.nagpal.shivam.vtucslab.R;
-import com.nagpal.shivam.vtucslab.Utility.ConstantVariables;
-
-import java.util.ArrayList;
-
-public class ProgramActivity
- extends AppCompatActivity
- implements LoaderManager.LoaderCallbacks,
- ContentAdapter.ItemClickHandler {
-
- public static final String INTENT_LABORATORY = "PROGRAM_INTENT_LABORATORY";
- public static final String INTENT_LABORATORY_BASE_URL = "PROGRAM_INTENT_LABORATORY_BASE_URL";
-
- private static final int REPO_LOADER_ID = 2;
- private static final String SUCCEEDED_KEY = "succeeded_key";
-
- private ContentAdapter mProgramAdapter;
- private ProgressBar mProgressBar;
- private RecyclerView mProgramRecyclerView;
- private TextView mEmptyTextView;
- private boolean mSucceeded;
- private String mProgramBaseUrl;
- private LoaderManager mLoaderManager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_program);
-
- if (savedInstanceState != null) {
- mSucceeded = savedInstanceState.getBoolean(SUCCEEDED_KEY, false);
- }
-
- initAndSetupViews();
-
- setupProgramAdapter();
-
- loadPrograms();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(ProgramActivity.this);
- return true;
-
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void initAndSetupViews() {
- mProgramRecyclerView = findViewById(R.id.activity_program_recycler_view_program);
- mProgramRecyclerView.setLayoutManager(new LinearLayoutManager(ProgramActivity.this, LinearLayoutManager.VERTICAL, false));
- mProgramRecyclerView.setHasFixedSize(true);
-
- mEmptyTextView = findViewById(R.id.activity_program_text_view_empty);
-
- mProgressBar = findViewById(R.id.activity_program_progress_bar);
- }
-
- private void setupProgramAdapter() {
- mProgramAdapter = new ContentAdapter(ProgramActivity.this, new ArrayList());
- mProgramRecyclerView.setAdapter(mProgramAdapter);
- mProgramAdapter.setItemClickHandler(this);
- }
-
- private void loadPrograms() {
- Intent intent = getIntent();
-
- Laboratory laboratory = (Laboratory) intent.getSerializableExtra(INTENT_LABORATORY);
- String laboratoryBaseUrl = intent.getStringExtra(INTENT_LABORATORY_BASE_URL);
-
- ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connectivityManager != null ? connectivityManager.getActiveNetworkInfo() : null;
- mLoaderManager = LoaderManager.getInstance(this);
-
- if (!mSucceeded) {
- mLoaderManager.destroyLoader(REPO_LOADER_ID);
- }
-
- mEmptyTextView.setVisibility(View.GONE);
-
- setTitle(laboratory.getTitle());
- if (networkInfo != null && networkInfo.isConnected()) {
- Bundle bundle = new Bundle();
- String url = laboratoryBaseUrl + "/" + laboratory.getFileName();
-
- bundle.putString("URL", url);
- mLoaderManager.initLoader(REPO_LOADER_ID, bundle, ProgramActivity.this);
- } else {
- mProgressBar.setVisibility(View.GONE);
- mSucceeded = false;
- showErrorMessage(getString(R.string.no_internet_connection));
- }
- }
-
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- return new InfoLoader(ProgramActivity.this, args.getString("URL"));
- }
-
- @Override
- public void onLoadFinished(Loader loader, LabResponse labResponse) {
- mProgressBar.setVisibility(View.GONE);
-
- if (labResponse == null) {
- mSucceeded = false;
- Toast.makeText(ProgramActivity.this, getString(R.string.error_occurred), Toast.LENGTH_LONG).show();
- return;
- }
-
- if (labResponse.isValid()) {
- mProgramBaseUrl = labResponse.getGithub_raw_content() + "/" +
- labResponse.getOrganization() + "/" +
- labResponse.getRepository() + "/" +
- labResponse.getBranch();
- mSucceeded = true;
- mProgramAdapter.clear();
- mProgramAdapter.addAll(labResponse.getLabExperiments());
-// mLinkToRepo = labResponse.getLinkToRepo();
-// invalidateOptionsMenu();
- } else {
- mSucceeded = false;
- showErrorMessage(labResponse.getInvalidationMessage());
- }
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- mProgramAdapter.clear();
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(SUCCEEDED_KEY, mSucceeded);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onContentFileClick(ContentFile file) {
- Intent intent = new Intent(ProgramActivity.this, DisplayActivity.class);
- intent.putExtra(ConstantVariables.title_intent_tag, file.getFileName());
- intent.putExtra(ConstantVariables.url_intent_tag, mProgramBaseUrl + "/" + file.getFileName());
- startActivity(intent);
- }
-
- private void showErrorMessage(String error) {
- mEmptyTextView.setVisibility(View.VISIBLE);
- mEmptyTextView.setText(error);
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/RepositoryActivity.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/RepositoryActivity.java
deleted file mode 100644
index 5164dea..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Activity/RepositoryActivity.java
+++ /dev/null
@@ -1,247 +0,0 @@
-package com.nagpal.shivam.vtucslab.Activity;
-
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.Gravity;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBarDrawerToggle;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.drawerlayout.widget.DrawerLayout;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.navigation.NavigationView;
-import com.nagpal.shivam.vtucslab.Adapter.NavigationAdapter;
-import com.nagpal.shivam.vtucslab.Loader.InfoLoader;
-import com.nagpal.shivam.vtucslab.Model.LabResponse;
-import com.nagpal.shivam.vtucslab.Model.Laboratory;
-import com.nagpal.shivam.vtucslab.R;
-
-import java.util.ArrayList;
-
-public class RepositoryActivity
- extends AppCompatActivity
- implements NavigationView.OnNavigationItemSelectedListener,
- LoaderManager.LoaderCallbacks,
- NavigationAdapter.NavigationAdapterItemClickHandler {
-
- private static final int NAV_LOADER_ID = 1;
- private static final String SUCCEEDED_KEY = "succeeded_key";
- private static final String URL = "https://raw.githubusercontent.com/vtucs/Index_v3/master/Index_v3.json";
-// private static final String URL = "https://raw.githubusercontent.com/vtucs/Test_Index/master/TestIndex.json";
-
- private DrawerLayout mDrawerLayout;
- private LoaderManager mLoaderManager;
- private NavigationAdapter mRepositoryAdapter;
- private NavigationView mNavigationView;
- private ProgressBar mProgressBar;
- private RecyclerView mRepositoryRecyclerView;
- private TextView mEmptyTextView;
- private Toolbar mToolbar;
- private View mHeaderView;
- private boolean mSucceeded;
- private String mLaboratoryBaseUrl;
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu_main_activity, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.main_menu_item_privacy:
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse("https://github.com/ShivamNagpal/Privacy_Policies/blob/master/VTU_CS_LAB_MANUAL.md"));
- startActivity(intent);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
-
- @Override
- public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
- boolean flag = false;
- switch (menuItem.getItemId()) {
- case R.id.menu_item_repository:
- flag = true;
- break;
- case R.id.menu_item_exit:
- exitApplication();
- flag = true;
- break;
- }
- if (flag) {
- closeNavigationDrawer();
- }
- return flag;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_repository);
- mToolbar = findViewById(R.id.toolbar);
- setSupportActionBar(mToolbar);
-
- if (savedInstanceState != null) {
- mSucceeded = savedInstanceState.getBoolean(SUCCEEDED_KEY, false);
- }
-
- initAndSetupViews();
-
- setupRepositoryAdapter();
-
- loadRepositories();
- }
-
- private void loadRepositories() {
- ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connectivityManager != null ? connectivityManager.getActiveNetworkInfo() : null;
- mLoaderManager = LoaderManager.getInstance(this);
-
- if (!mSucceeded) {
- mLoaderManager.destroyLoader(NAV_LOADER_ID);
- }
-
- if (networkInfo != null && networkInfo.isConnected()) {
- mLoaderManager.initLoader(NAV_LOADER_ID, null, RepositoryActivity.this);
- } else {
- mProgressBar.setVisibility(View.GONE);
- mSucceeded = false;
- showErrorMessage(getString(R.string.no_internet_connection));
- }
-
- mRepositoryAdapter.setNavigationAdapterItemClickHandler(this);
- }
-
- private void initAndSetupViews() {
- mDrawerLayout = findViewById(R.id.activity_repository_drawer_layout);
- setUpDrawerToggle();
-
- mNavigationView = findViewById(R.id.activity_repository_navigation_view);
- mNavigationView.setNavigationItemSelectedListener(this);
- mNavigationView.getMenu().getItem(0).setChecked(true);
-
- mHeaderView = mNavigationView.getHeaderView(0);
-
- ImageButton navigationDrawerBackButton = mHeaderView.findViewById(R.id.activity_repository_image_button_close_drawer);
- navigationDrawerBackButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- closeNavigationDrawer();
- }
- });
-
- mRepositoryRecyclerView = findViewById(R.id.activity_repository_recycler_view_repository);
- mRepositoryRecyclerView.setLayoutManager(new LinearLayoutManager(RepositoryActivity.this, LinearLayoutManager.VERTICAL, false));
- mRepositoryRecyclerView.setHasFixedSize(true);
-
- mEmptyTextView = findViewById(R.id.empty_text_view_main);
-
- mProgressBar = findViewById(R.id.progress_bar_main);
- }
-
- private void setUpDrawerToggle() {
- ActionBarDrawerToggle actionBarDrawerToggle =
- new ActionBarDrawerToggle(RepositoryActivity.this,
- mDrawerLayout,
- mToolbar,
- R.string.drawer_open,
- R.string.drawer_close);
- mDrawerLayout.addDrawerListener(actionBarDrawerToggle);
- actionBarDrawerToggle.syncState();
- }
-
- private void closeNavigationDrawer() {
- mDrawerLayout.closeDrawer(Gravity.START, true);
- }
-
- private void exitApplication() {
- this.finishAffinity();
- new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
- @Override
- public void run() {
- System.exit(0);
- }
- }, 1000);
- }
-
- private void setupRepositoryAdapter() {
- mRepositoryAdapter = new NavigationAdapter(RepositoryActivity.this, new ArrayList());
- mRepositoryRecyclerView.setAdapter(mRepositoryAdapter);
- }
-
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- return new InfoLoader(RepositoryActivity.this, URL);
-
- }
-
- @Override
- public void onLoadFinished(Loader loader, LabResponse labResponse) {
- mProgressBar.setVisibility(View.GONE);
-
- if (labResponse == null) {
- mSucceeded = false;
- Toast.makeText(RepositoryActivity.this, getString(R.string.error_occurred), Toast.LENGTH_LONG).show();
- return;
- }
-
- if (labResponse.isValid()) {
- mLaboratoryBaseUrl = labResponse.getGithub_raw_content() + "/" +
- labResponse.getOrganization() + "/" +
- labResponse.getRepository() + "/" +
- labResponse.getBranch();
-
- mRepositoryAdapter.clear();
- mRepositoryAdapter.addAll(labResponse.getLaboratories());
- } else {
- mSucceeded = false;
- showErrorMessage(labResponse.getInvalidationMessage());
- }
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- mRepositoryAdapter.clear();
- }
-
- @Override
- public void onNavigationAdapterItemClick(Laboratory laboratory, int i) {
- Intent intent = new Intent(RepositoryActivity.this, ProgramActivity.class);
- intent.putExtra(ProgramActivity.INTENT_LABORATORY, laboratory);
- intent.putExtra(ProgramActivity.INTENT_LABORATORY_BASE_URL, mLaboratoryBaseUrl);
- startActivity(intent);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(SUCCEEDED_KEY, mSucceeded);
- super.onSaveInstanceState(outState);
- }
-
- private void showErrorMessage(String error) {
- mEmptyTextView.setVisibility(View.VISIBLE);
- mEmptyTextView.setText(error);
- }
-
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/ContentAdapter.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/ContentAdapter.java
deleted file mode 100644
index 67e15f6..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/ContentAdapter.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package com.nagpal.shivam.vtucslab.Adapter;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.databinding.DataBindingUtil;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.nagpal.shivam.vtucslab.Model.ContentFile;
-import com.nagpal.shivam.vtucslab.Model.LabExperiment;
-import com.nagpal.shivam.vtucslab.Model.LabExperimentSubPart;
-import com.nagpal.shivam.vtucslab.R;
-import com.nagpal.shivam.vtucslab.Utility.StaticMethods;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeMspBinding;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeSspMfBinding;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeSspSfBinding;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class ContentAdapter extends RecyclerView.Adapter {
-
- private static final int VIEW_TYPE_SSP_SF = 0;
- private static final int VIEW_TYPE_MSP_SF = 1;
- private static final int VIEW_TYPE_SSP_MF = 2;
- private static final int VIEW_TYPE_MSP_MF = 3;
- private static final int VIEW_TYPE_INVALID = -1;
- private Context mContext;
- private ArrayList mLabExperimentArrayList;
- private ItemClickHandler mItemClickHandler;
-
- public ContentAdapter(Context context, ArrayList labExperimentArrayList) {
- this.mContext = context;
- this.mLabExperimentArrayList = labExperimentArrayList;
- }
-
- public void setItemClickHandler(ItemClickHandler itemClickHandler) {
- mItemClickHandler = itemClickHandler;
- }
-
- public void addAll(ArrayList labExperimentArrayList) {
- int i = mLabExperimentArrayList.size();
- mLabExperimentArrayList.addAll(labExperimentArrayList);
- notifyItemRangeInserted(i, labExperimentArrayList.size());
- }
-
- public void addAll(@NonNull LabExperiment[] labExperiments) {
- int i = mLabExperimentArrayList.size();
- mLabExperimentArrayList.addAll(Arrays.asList(labExperiments));
- notifyItemRangeInserted(i, labExperiments.length);
- }
-
-
- public void clear() {
- mLabExperimentArrayList.clear();
- notifyDataSetChanged();
- }
-
- @NonNull
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_SSP_SF:
- LayoutCardSeSspSfBinding sspSfBinding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_se_ssp_sf, parent, false);
- return new SspSfViewHolder(sspSfBinding);
- case VIEW_TYPE_MSP_SF:
- LayoutCardSeMspBinding mspSfBinding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_se_msp, parent, false);
- return new MspSfViewHolder(mspSfBinding);
- case VIEW_TYPE_SSP_MF:
- LayoutCardSeSspMfBinding sspMfBinding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_se_ssp_mf, parent, false);
- return new SspMfViewHolder(sspMfBinding);
- case VIEW_TYPE_MSP_MF:
- LayoutCardSeMspBinding seMspBinding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_se_msp, parent, false);
- return new MspMfViewHolder(seMspBinding);
- default:
- return new InvalidViewHolder(new View(mContext));
-
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
- LabExperiment labExperiment = mLabExperimentArrayList.get(position);
- if (holder instanceof SspSfViewHolder) {
- SspSfViewHolder sspSfViewHolder = (SspSfViewHolder) holder;
- sspSfViewHolder.mBinding.serialOrder.setText(processSerialOrder(labExperiment.getSerialOrder()));
- String[] parts = labExperiment.getLabExperimentSubParts()[0].getContentFiles()[0].getFileName().split("\\.");
- if (parts.length >= 2) {
- sspSfViewHolder.mBinding.programTitle.setText(StaticMethods.formatProgramName(parts[parts.length - 2]));
- }
- } else if (holder instanceof MspSfViewHolder) {
- MspSfViewHolder mspSfViewHolder = (MspSfViewHolder) holder;
- mspSfViewHolder.mBinding.serialOrder.setText(processSerialOrder(labExperiment.getSerialOrder()));
- MultipleSubPartAdapter adapter = new MultipleSubPartAdapter(mContext, labExperiment.getLabExperimentSubParts(), false, mItemClickHandler);
- mspSfViewHolder.mBinding.subPartContainer.setAdapter(adapter);
- } else if (holder instanceof SspMfViewHolder) {
- SspMfViewHolder sspMfViewHolder = (SspMfViewHolder) holder;
- sspMfViewHolder.mBinding.serialOrder.setText(processSerialOrder(labExperiment.getSerialOrder()));
- MultipleFileAdapter adapter = new MultipleFileAdapter(mContext, R.layout.layout_card_single_files_without_sub_parts, labExperiment.getLabExperimentSubParts()[0].getContentFiles(), mItemClickHandler);
- sspMfViewHolder.mBinding.filesContainer.setAdapter(adapter);
- } else if (holder instanceof MspMfViewHolder) {
- MspMfViewHolder mspMfViewHolder = (MspMfViewHolder) holder;
- mspMfViewHolder.mBinding.serialOrder.setText(processSerialOrder(labExperiment.getSerialOrder()));
- MultipleSubPartAdapter adapter = new MultipleSubPartAdapter(mContext, labExperiment.getLabExperimentSubParts(), true, mItemClickHandler);
- mspMfViewHolder.mBinding.subPartContainer.setAdapter(adapter);
- }
- }
-
- @Override
- public int getItemCount() {
- return mLabExperimentArrayList.size();
- }
-
- @Override
- public int getItemViewType(int position) {
- LabExperiment experiment = mLabExperimentArrayList.get(position);
- int contentFileLength = 1;
- LabExperimentSubPart[] labExperimentSubParts = experiment.getLabExperimentSubParts();
- for (int i = 0, labExperimentSubPartsLength = labExperimentSubParts.length; i < labExperimentSubPartsLength; i++) {
- LabExperimentSubPart subPart = labExperimentSubParts[i];
- contentFileLength = Math.max(contentFileLength, subPart.getContentFiles().length);
- }
- if (labExperimentSubParts.length == 1 && contentFileLength == 1)
- return VIEW_TYPE_SSP_SF;
- else if (labExperimentSubParts.length > 1 && contentFileLength == 1)
- return VIEW_TYPE_MSP_SF;
- else if (labExperimentSubParts.length == 1 && contentFileLength > 1)
- return VIEW_TYPE_SSP_MF;
- else if (labExperimentSubParts.length > 1 && contentFileLength > 1)
- return VIEW_TYPE_MSP_MF;
- else
- return VIEW_TYPE_INVALID;
- }
-
- private String processSerialOrder(String order) {
- try {
- int i = Integer.parseInt(order);
- return String.valueOf(i);
- } catch (NumberFormatException e) {
- return order;
- }
- }
-
- public interface ItemClickHandler {
- void onContentFileClick(ContentFile file);
- }
-
- class SspSfViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
- LayoutCardSeSspSfBinding mBinding;
-
- SspSfViewHolder(LayoutCardSeSspSfBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- binding.getRoot().setOnClickListener(this);
- }
-
- @Override
- public void onClick(View view) {
- if (mItemClickHandler != null) {
- int position = getAdapterPosition();
- ContentFile contentFile = mLabExperimentArrayList.get(position).getLabExperimentSubParts()[0].getContentFiles()[0];
- mItemClickHandler.onContentFileClick(contentFile);
- }
- }
- }
-
- class MspSfViewHolder extends RecyclerView.ViewHolder {
- LayoutCardSeMspBinding mBinding;
-
- MspSfViewHolder(LayoutCardSeMspBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- mBinding.subPartContainer.setLayoutManager(new LinearLayoutManager(mContext));
- mBinding.subPartContainer.setHasFixedSize(true);
- }
- }
-
- class SspMfViewHolder extends RecyclerView.ViewHolder {
- LayoutCardSeSspMfBinding mBinding;
-
- SspMfViewHolder(LayoutCardSeSspMfBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- mBinding.filesContainer.setLayoutManager(new LinearLayoutManager(mContext));
- mBinding.filesContainer.setHasFixedSize(true);
- }
- }
-
- class MspMfViewHolder extends RecyclerView.ViewHolder {
- LayoutCardSeMspBinding mBinding;
-
- MspMfViewHolder(LayoutCardSeMspBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- mBinding.subPartContainer.setLayoutManager(new LinearLayoutManager(mContext));
- mBinding.subPartContainer.setHasFixedSize(true);
- }
- }
-
- class InvalidViewHolder extends RecyclerView.ViewHolder {
- InvalidViewHolder(@NonNull View itemView) {
- super(itemView);
- }
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleFileAdapter.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleFileAdapter.java
deleted file mode 100644
index 52b5161..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleFileAdapter.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.nagpal.shivam.vtucslab.Adapter;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.LayoutRes;
-import androidx.annotation.NonNull;
-import androidx.databinding.DataBindingUtil;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.nagpal.shivam.vtucslab.Model.ContentFile;
-import com.nagpal.shivam.vtucslab.Utility.StaticMethods;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleFilesWithoutSubPartsBinding;
-
-public class MultipleFileAdapter extends RecyclerView.Adapter {
- private Context mContext;
- private ContentFile[] mContentFiles;
- private ContentAdapter.ItemClickHandler mItemClickHandler;
- @LayoutRes
- private int mLayoutId;
-
- public MultipleFileAdapter(Context context, @LayoutRes int layoutId, ContentFile[] contentFiles, ContentAdapter.ItemClickHandler itemClickHandler) {
- mContext = context;
- mLayoutId = layoutId;
- mContentFiles = contentFiles;
- mItemClickHandler = itemClickHandler;
- }
-
- @NonNull
- @Override
- public ContentFileViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
- LayoutCardSingleFilesWithoutSubPartsBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), mLayoutId, viewGroup, false);
- return new ContentFileViewHolder(binding);
- }
-
- @Override
- public void onBindViewHolder(@NonNull ContentFileViewHolder contentFileViewHolder, int i) {
- String[] parts = mContentFiles[i].getFileName().split("\\.");
- if (parts.length >= 2) {
- contentFileViewHolder.mBinding.programTitle.setText(StaticMethods.formatProgramName((parts[parts.length - 2])));
- }
- }
-
- @Override
- public int getItemCount() {
- if (mContentFiles != null) {
- return mContentFiles.length;
- } else {
- return 0;
- }
- }
-
- class ContentFileViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-
- LayoutCardSingleFilesWithoutSubPartsBinding mBinding;
-
- ContentFileViewHolder(LayoutCardSingleFilesWithoutSubPartsBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- binding.getRoot().setOnClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- if (mItemClickHandler != null) {
- int position = getAdapterPosition();
- ContentFile file = mContentFiles[position];
- mItemClickHandler.onContentFileClick(file);
- }
- }
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleSubPartAdapter.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleSubPartAdapter.java
deleted file mode 100644
index 1b94ac7..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/MultipleSubPartAdapter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.nagpal.shivam.vtucslab.Adapter;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.databinding.DataBindingUtil;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.nagpal.shivam.vtucslab.Model.ContentFile;
-import com.nagpal.shivam.vtucslab.Model.LabExperimentSubPart;
-import com.nagpal.shivam.vtucslab.R;
-import com.nagpal.shivam.vtucslab.Utility.StaticMethods;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleSubPartsWithFilesBinding;
-import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleSubPartsWithoutFilesBinding;
-
-public class MultipleSubPartAdapter extends RecyclerView.Adapter {
- private Context mContext;
- private LabExperimentSubPart[] mSubParts;
- private boolean mContainsMultipleFiles;
- private ContentAdapter.ItemClickHandler mItemClickHandler;
-
- public MultipleSubPartAdapter(Context context, LabExperimentSubPart[] subParts, boolean containsMultipleFiles, ContentAdapter.ItemClickHandler itemClickHandler) {
- mContext = context;
- mSubParts = subParts;
- mContainsMultipleFiles = containsMultipleFiles;
- mItemClickHandler = itemClickHandler;
- }
-
- @NonNull
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
-
- if (!mContainsMultipleFiles) {
- LayoutCardSingleSubPartsWithoutFilesBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_single_sub_parts_without_files, viewGroup, false);
- return new SubPartWithoutFilesViewHolder(binding);
- } else {
- LayoutCardSingleSubPartsWithFilesBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.layout_card_single_sub_parts_with_files, viewGroup, false);
- return new SubPartWithFilesViewHolder(binding);
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int i) {
- if (holder instanceof SubPartWithoutFilesViewHolder) {
- SubPartWithoutFilesViewHolder subPartWithoutFilesViewHolder = (SubPartWithoutFilesViewHolder) holder;
- subPartWithoutFilesViewHolder.mBinding.serialOrder.setText(mSubParts[i].getSubSerialOrder());
- String[] parts = mSubParts[i].getContentFiles()[0].getFileName().split("\\.");
- if (parts.length >= 2) {
- subPartWithoutFilesViewHolder.mBinding.programTitle.setText(StaticMethods.formatProgramName(parts[parts.length - 2]));
- }
- } else if (holder instanceof SubPartWithFilesViewHolder) {
- SubPartWithFilesViewHolder subPartWithFilesViewHolder = (SubPartWithFilesViewHolder) holder;
- subPartWithFilesViewHolder.mBinding.serialOrder.setText(mSubParts[i].getSubSerialOrder());
- MultipleFileAdapter adapter = new MultipleFileAdapter(mContext, R.layout.layout_card_single_files_without_sub_parts, mSubParts[i].getContentFiles(), mItemClickHandler);
- subPartWithFilesViewHolder.mBinding.filesContainer.setAdapter(adapter);
- }
- }
-
- @Override
- public int getItemCount() {
- if (mSubParts != null) {
- return mSubParts.length;
- }
- return 0;
- }
-
- class SubPartWithoutFilesViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-
- LayoutCardSingleSubPartsWithoutFilesBinding mBinding;
-
- SubPartWithoutFilesViewHolder(LayoutCardSingleSubPartsWithoutFilesBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- binding.getRoot().setOnClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- if (mItemClickHandler != null) {
- int position = getAdapterPosition();
- ContentFile contentFile = mSubParts[position].getContentFiles()[0];
- mItemClickHandler.onContentFileClick(contentFile);
- }
- }
- }
-
- class SubPartWithFilesViewHolder extends RecyclerView.ViewHolder {
-
- LayoutCardSingleSubPartsWithFilesBinding mBinding;
-
- SubPartWithFilesViewHolder(LayoutCardSingleSubPartsWithFilesBinding binding) {
- super(binding.getRoot());
- mBinding = binding;
- mBinding.filesContainer.setLayoutManager(new LinearLayoutManager(mContext));
- mBinding.filesContainer.setHasFixedSize(true);
- }
-
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/NavigationAdapter.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/NavigationAdapter.java
deleted file mode 100644
index cfc3a57..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Adapter/NavigationAdapter.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.nagpal.shivam.vtucslab.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.nagpal.shivam.vtucslab.Model.Laboratory;
-import com.nagpal.shivam.vtucslab.R;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class NavigationAdapter extends RecyclerView.Adapter {
-
- private Context mContext;
- private ArrayList mLaboratoryArrayList;
- private NavigationAdapterItemClickHandler mNavigationAdapterItemClickHandler;
-
- public NavigationAdapter(@NonNull Context context, @NonNull ArrayList laboratoryArrayList) {
- mContext = context;
- mLaboratoryArrayList = laboratoryArrayList;
- }
-
- public void addAll(ArrayList laboratoryArrayList) {
- int i = mLaboratoryArrayList.size();
- mLaboratoryArrayList.addAll(laboratoryArrayList);
- notifyItemRangeInserted(i, laboratoryArrayList.size());
- }
-
- public void addAll(Laboratory[] laboratories) {
- int i = mLaboratoryArrayList.size();
- mLaboratoryArrayList.addAll(Arrays.asList(laboratories));
- notifyItemRangeInserted(i, laboratories.length);
- }
-
- public void clear() {
- mLaboratoryArrayList.clear();
- notifyDataSetChanged();
- }
-
- public void setNavigationAdapterItemClickHandler(NavigationAdapterItemClickHandler navigationAdapterItemClickHandler) {
- mNavigationAdapterItemClickHandler = navigationAdapterItemClickHandler;
- }
-
- @NonNull
- @Override
- public NavigationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(mContext).inflate(R.layout.layout_card_repository, parent, false);
- return new NavigationViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(@NonNull NavigationViewHolder holder, int position) {
- Laboratory laboratory = mLaboratoryArrayList.get(position);
- String fileName = laboratory.getFileName();
- String[] parts = fileName.split("\\.");
- laboratory.setTitle(parts[0].replace('_', ' '));
- holder.mTextView.setText(laboratory.getTitle());
- }
-
- @Override
- public int getItemCount() {
- return mLaboratoryArrayList.size();
- }
-
- public interface NavigationAdapterItemClickHandler {
- void onNavigationAdapterItemClick(Laboratory laboratory, int i);
- }
-
- class NavigationViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
- TextView mTextView;
-
- NavigationViewHolder(View itemView) {
- super(itemView);
- mTextView = itemView.findViewById(R.id.text_view_layout);
- itemView.setOnClickListener(this);
- }
-
- @Override
- public void onClick(View view) {
- if (mNavigationAdapterItemClickHandler != null) {
- int i = getAdapterPosition();
- mNavigationAdapterItemClickHandler.onNavigationAdapterItemClick(mLaboratoryArrayList.get(i), i);
- }
- }
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/InfoLoader.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/InfoLoader.java
deleted file mode 100644
index b0786d3..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/InfoLoader.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.nagpal.shivam.vtucslab.Loader;
-
-
-import android.content.Context;
-import android.text.TextUtils;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-import com.google.gson.Gson;
-import com.nagpal.shivam.vtucslab.Model.LabResponse;
-import com.nagpal.shivam.vtucslab.Utility.FetchUtil;
-
-public class InfoLoader extends AsyncTaskLoader {
- private String mUrl;
- private LabResponse mLabResponse;
-
- public InfoLoader(Context context, String url) {
- super(context);
- mUrl = url;
- }
-
- @Override
- protected void onStartLoading() {
- if (mLabResponse != null) {
- deliverResult(mLabResponse);
- } else {
- forceLoad();
- }
-
- }
-
- @Override
- public LabResponse loadInBackground() {
- if (mUrl == null) {
- return null;
- }
- String jsonResponse = FetchUtil.fetchData(mUrl);
- mLabResponse = extractFeaturesFromJson(jsonResponse);
- return mLabResponse;
- }
-
- private LabResponse extractFeaturesFromJson(String jsonResponse) {
- if (TextUtils.isEmpty(jsonResponse)) {
- return null;
- }
- Gson gson = new Gson();
- LabResponse labResponse = gson.fromJson(jsonResponse, LabResponse.class);
- return labResponse;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/RawStreamLoader.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/RawStreamLoader.java
deleted file mode 100644
index ddf22ca..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Loader/RawStreamLoader.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.nagpal.shivam.vtucslab.Loader;
-
-import android.content.Context;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-import com.nagpal.shivam.vtucslab.Utility.FetchUtil;
-
-public class RawStreamLoader extends AsyncTaskLoader {
- private static final String RAW_STREAM_LOADER = "raw_stream_loader";
- private String mUrl;
- private String fetchedData;
-
- // private SharedPreferences sharedPreferences;
-// TODO: Implement Local Data Saving.
- public RawStreamLoader(Context context, String url) {
- super(context);
- mUrl = url;
-// sharedPreferences = context.getSharedPreferences(RAW_STREAM_LOADER, Context.MODE_PRIVATE);
-// fetchedData = sharedPreferences.getString(mUrl, null);
- }
-
- @Override
- protected void onStartLoading() {
- if (fetchedData != null) {
- deliverResult(fetchedData);
- } else {
- forceLoad();
- }
- }
-
- @Override
- public String loadInBackground() {
- fetchedData = FetchUtil.fetchData(mUrl);
-// TODO: If fetched data is not null.
-// SharedPreferences.Editor editor = sharedPreferences.edit();
-// editor.putString(mUrl, fetchedData);
-// editor.apply();
- return fetchedData;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/ContentFile.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Model/ContentFile.java
deleted file mode 100644
index e6e087d..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/ContentFile.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.nagpal.shivam.vtucslab.Model;
-
-public class ContentFile {
- private String fileName;
-
- public String getFileName() {
- return fileName;
- }
-
- public void setFileName(String fileName) {
- this.fileName = fileName;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperiment.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperiment.java
deleted file mode 100644
index 236555b..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperiment.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.nagpal.shivam.vtucslab.Model;
-
-public class LabExperiment {
-
- private String serialOrder;
- private LabExperimentSubPart[] labExperimentSubParts;
-
- public String getSerialOrder() {
- return serialOrder;
- }
-
- public LabExperiment setSerialOrder(String serialOrder) {
- this.serialOrder = serialOrder;
- return this;
- }
-
- public LabExperimentSubPart[] getLabExperimentSubParts() {
- return labExperimentSubParts;
- }
-
- public LabExperiment setLabExperimentSubParts(LabExperimentSubPart[] labExperimentSubParts) {
- this.labExperimentSubParts = labExperimentSubParts;
- return this;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperimentSubPart.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperimentSubPart.java
deleted file mode 100644
index 61683a0..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabExperimentSubPart.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.nagpal.shivam.vtucslab.Model;
-
-public class LabExperimentSubPart {
-
- private String subSerialOrder;
- private ContentFile[] contentFiles;
-
- public String getSubSerialOrder() {
- return subSerialOrder;
- }
-
- public LabExperimentSubPart setSubSerialOrder(String subSerialOrder) {
- this.subSerialOrder = subSerialOrder;
- return this;
- }
-
- public ContentFile[] getContentFiles() {
- return contentFiles;
- }
-
- public LabExperimentSubPart setContentFiles(ContentFile[] contentFiles) {
- this.contentFiles = contentFiles;
- return this;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabResponse.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabResponse.java
deleted file mode 100644
index e7421d3..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/LabResponse.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.nagpal.shivam.vtucslab.Model;
-
-public class LabResponse {
- private String context;
- private boolean isValid = true;
- private String invalidationMessage;
- private Laboratory[] laboratories;
- private LabExperiment[] labExperiments;
- private String github_raw_content;
- private String organization;
- private String repository;
- private String branch;
-
- public String getContext() {
- return context;
- }
-
- public void setContext(String context) {
- this.context = context;
- }
-
- public boolean isValid() {
- return isValid;
- }
-
- public void setValid(boolean valid) {
- isValid = valid;
- }
-
- public String getInvalidationMessage() {
- return invalidationMessage;
- }
-
- public void setInvalidationMessage(String invalidationMessage) {
- this.invalidationMessage = invalidationMessage;
- }
-
- public LabExperiment[] getLabExperiments() {
- return labExperiments;
- }
-
- public LabResponse setLabExperiments(LabExperiment[] labExperiments) {
- this.labExperiments = labExperiments;
- return this;
- }
-
- public Laboratory[] getLaboratories() {
- return laboratories;
- }
-
- public LabResponse setLaboratories(Laboratory[] laboratories) {
- this.laboratories = laboratories;
- return this;
- }
-
- public String getGithub_raw_content() {
- return github_raw_content;
- }
-
- public void setGithub_raw_content(String github_raw_content) {
- this.github_raw_content = github_raw_content;
- }
-
- public String getOrganization() {
- return organization;
- }
-
- public void setOrganization(String organization) {
- this.organization = organization;
- }
-
- public String getRepository() {
- return repository;
- }
-
- public void setRepository(String repository) {
- this.repository = repository;
- }
-
- public String getBranch() {
- return branch;
- }
-
- public void setBranch(String branch) {
- this.branch = branch;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/Laboratory.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Model/Laboratory.java
deleted file mode 100644
index 7ae3a03..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Model/Laboratory.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.nagpal.shivam.vtucslab.Model;
-
-import java.io.Serializable;
-
-public class Laboratory implements Serializable {
- private String title;
- private String fileName;
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getFileName() {
- return fileName;
- }
-
- public void setFileName(String fileName) {
- this.fileName = fileName;
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/ConstantVariables.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/ConstantVariables.java
deleted file mode 100644
index c1738c5..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/ConstantVariables.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.nagpal.shivam.vtucslab.Utility;
-
-public class ConstantVariables {
- public static final String title_intent_tag = "title";
- public static final String url_intent_tag = "url";
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/FetchUtil.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/FetchUtil.java
deleted file mode 100644
index 39889b3..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/FetchUtil.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.nagpal.shivam.vtucslab.Utility;
-
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.charset.Charset;
-
-import static java.net.HttpURLConnection.HTTP_OK;
-
-public class FetchUtil {
-
- private static final String LOG_TAG = "FetchUtils";
-
- public FetchUtil() {
- }
-
- public static String fetchData(String requestUrl) {
- Log.i(LOG_TAG, "Fetching Data.");
- URL url = createUrl(requestUrl);
- String jsonResponse = "";
- try {
- jsonResponse = makeHttpRequest(url);
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error while making HTTP request.", e);
- }
- return jsonResponse;
- }
-
- private static URL createUrl(String stringUrl) {
- URL url = null;
- try {
- url = new URL(stringUrl);
- } catch (MalformedURLException e) {
- Log.e(LOG_TAG, "Error with url creation", e);
- }
- return url;
- }
-
- private static String makeHttpRequest(URL url) throws IOException {
- String jsonResponse = "";
- if (url == null) {
- return jsonResponse;
- }
-
- HttpURLConnection urlConnection = null;
- InputStream inputStream = null;
- try {
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setReadTimeout(10000);
- urlConnection.setConnectTimeout(15000);
- urlConnection.setRequestMethod("GET");
- urlConnection.connect();
-
- int responseCode = urlConnection.getResponseCode();
- if (responseCode == HTTP_OK) {
- inputStream = urlConnection.getInputStream();
- jsonResponse = readFromStream(inputStream);
- } else {
- Log.e(LOG_TAG, "Error Response Code: " + responseCode);
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "Problem retrieving JSON response\n");
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- if (inputStream != null) {
- inputStream.close();
- }
- }
- return jsonResponse;
- }
-
- private static String readFromStream(InputStream inputStream) throws IOException {
- StringBuilder stringBuilder = new StringBuilder();
- if (inputStream != null) {
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- String line = bufferedReader.readLine();
- while (line != null) {
-// line = line.replaceAll("\t", "\t\t");
- //TODO: Temporary Solution to add padding with white spaces
- stringBuilder.append(" ");
- stringBuilder.append(line);
- stringBuilder.append(" \n");
- line = bufferedReader.readLine();
- }
- }
- return stringBuilder.toString();
- }
-
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/StaticMethods.java b/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/StaticMethods.java
deleted file mode 100644
index e466ed7..0000000
--- a/app/src/main/java/com/nagpal/shivam/vtucslab/Utility/StaticMethods.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.nagpal.shivam.vtucslab.Utility;
-
-public class StaticMethods {
-
- public static String formatProgramName(String programName) {
- return programName.replace('_', ' ');
- }
-}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/VTUCSLabApplication.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/VTUCSLabApplication.kt
new file mode 100644
index 0000000..d390c02
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/VTUCSLabApplication.kt
@@ -0,0 +1,42 @@
+package com.nagpal.shivam.vtucslab
+
+import androidx.multidex.MultiDexApplication
+import androidx.room.Room
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.nagpal.shivam.vtucslab.data.local.AppDatabase
+import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepository
+import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepositoryImpl
+import com.nagpal.shivam.vtucslab.services.VtuCsLabService
+import com.nagpal.shivam.vtucslab.utilities.Constants.VTU_CS_LAB
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods
+
+class VTUCSLabApplication : MultiDexApplication() {
+ private lateinit var _db: AppDatabase
+ val db: AppDatabase
+ get() = _db
+
+ private lateinit var _vtuCsLabRepository: VtuCsLabRepository
+ val vtuCsLabRepository: VtuCsLabRepository
+ get() = _vtuCsLabRepository
+
+ override fun onCreate() {
+ super.onCreate()
+ if (BuildConfig.DEBUG) {
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
+ }
+
+ _db = Room.databaseBuilder(
+ applicationContext,
+ AppDatabase::class.java,
+ VTU_CS_LAB
+ ).build()
+
+ val vtuCsLabService = VtuCsLabService.instance
+ _vtuCsLabRepository = VtuCsLabRepositoryImpl(
+ this,
+ vtuCsLabService,
+ _db.labResponseDao(),
+ StaticMethods.jsonMapper
+ )
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/activities/MainActivity.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/activities/MainActivity.kt
new file mode 100644
index 0000000..d859f82
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/activities/MainActivity.kt
@@ -0,0 +1,137 @@
+package com.nagpal.shivam.vtucslab.activities
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.addCallback
+import androidx.appcompat.app.ActionBarDrawerToggle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.GravityCompat
+import androidx.drawerlayout.widget.DrawerLayout
+import androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED
+import androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.navigateUp
+import androidx.navigation.ui.onNavDestinationSelected
+import androidx.navigation.ui.setupActionBarWithNavController
+import androidx.navigation.ui.setupWithNavController
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.databinding.ActivityMainBinding
+import kotlin.system.exitProcess
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityMainBinding
+ private lateinit var navController: NavController
+ private lateinit var appBarConfiguration: AppBarConfiguration
+ private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.navigation_host_fragment) as NavHostFragment
+ navController = navHostFragment.navController
+
+ setupActionBar()
+ setupNavigationView()
+ setupDrawerToggle()
+ }
+
+ private fun setupActionBar() {
+ appBarConfiguration =
+ AppBarConfiguration(setOf(R.id.repositoryFragment), binding.drawerLayout)
+ setupActionBarWithNavController(navController, appBarConfiguration)
+ }
+
+ private fun setupNavigationView() {
+ binding.navigationView.setupWithNavController(navController)
+ binding.navigationView.setNavigationItemSelectedListener {
+ var flag = false
+ val itemId: Int = it.itemId
+ if (itemId == R.id.repositoryFragment) {
+ flag = true
+ } else if (itemId == R.id.menu_item_exit) {
+ exitApplication()
+ flag = true
+ }
+ if (flag) {
+ closeNavigationDrawer()
+ }
+ return@setNavigationItemSelectedListener flag
+ }
+ }
+
+ private fun setupDrawerToggle() {
+ actionBarDrawerToggle = ActionBarDrawerToggle(
+ this,
+ binding.drawerLayout,
+ R.string.drawer_open,
+ R.string.drawer_close
+ )
+ binding.drawerLayout.addDrawerListener(actionBarDrawerToggle)
+ actionBarDrawerToggle.syncState()
+
+ val drawerBackPressedCallback = onBackPressedDispatcher.addCallback(this, false) {
+ closeNavigationDrawer()
+ }
+ binding.drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
+ override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
+ }
+
+ override fun onDrawerOpened(drawerView: View) {
+ drawerBackPressedCallback.isEnabled = true
+ }
+
+ override fun onDrawerClosed(drawerView: View) {
+ drawerBackPressedCallback.isEnabled = false
+ }
+
+ override fun onDrawerStateChanged(newState: Int) {
+ }
+ })
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ if (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
+ binding.drawerLayout.setDrawerLockMode(LOCK_MODE_UNLOCKED, GravityCompat.START)
+ } else {
+ binding.drawerLayout.setDrawerLockMode(
+ LOCK_MODE_LOCKED_CLOSED,
+ GravityCompat.START
+ )
+ }
+ }
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home && appBarConfiguration.topLevelDestinations.contains(
+ navController.currentDestination?.id
+ )
+ ) {
+ actionBarDrawerToggle.syncState()
+ if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ binding.drawerLayout.closeDrawer(GravityCompat.START)
+ return true
+ }
+ }
+ return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
+ }
+
+
+ private fun exitApplication() {
+ finishAffinity()
+ Handler(Looper.getMainLooper()).postDelayed({
+ exitProcess(0)
+ }, 1000)
+ }
+
+ private fun closeNavigationDrawer() {
+ binding.drawerLayout.closeDrawer(GravityCompat.START, true)
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/ContentAdapter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/ContentAdapter.kt
new file mode 100644
index 0000000..1601138
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/ContentAdapter.kt
@@ -0,0 +1,229 @@
+package com.nagpal.shivam.vtucslab.adapters
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeMspBinding
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeSspMfBinding
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSeSspSfBinding
+import com.nagpal.shivam.vtucslab.models.ContentFile
+import com.nagpal.shivam.vtucslab.models.LabExperiment
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods.formatProgramName
+
+class ContentAdapter(
+ private val context: Context,
+ private val labExperimentArrayList: ArrayList
+) : RecyclerView.Adapter() {
+ private var itemClickHandler: ItemClickHandler? = null
+ fun setItemClickHandler(itemClickHandler: ItemClickHandler) {
+ this.itemClickHandler = itemClickHandler
+ }
+
+ fun addAll(labExperiments: List) {
+ val i = labExperimentArrayList.size
+ labExperimentArrayList.addAll(labExperiments)
+ notifyItemRangeInserted(i, labExperiments.size)
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun clear() {
+ labExperimentArrayList.clear()
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return when (viewType) {
+ VIEW_TYPE_SSP_SF -> {
+ val sspSfBinding =
+ LayoutCardSeSspSfBinding.inflate(
+ LayoutInflater.from(context),
+ parent,
+ false
+ )
+ SspSfViewHolder(sspSfBinding)
+ }
+
+ VIEW_TYPE_MSP_SF -> {
+ val mspSfBinding =
+ LayoutCardSeMspBinding.inflate(
+ LayoutInflater.from(context),
+ parent,
+ false
+ )
+ MspSfViewHolder(mspSfBinding)
+ }
+
+ VIEW_TYPE_SSP_MF -> {
+ val sspMfBinding =
+ LayoutCardSeSspMfBinding.inflate(
+ LayoutInflater.from(context),
+ parent,
+ false
+ )
+ SspMfViewHolder(sspMfBinding)
+ }
+
+ VIEW_TYPE_MSP_MF -> {
+ val seMspBinding =
+ LayoutCardSeMspBinding.inflate(
+ LayoutInflater.from(context),
+ parent,
+ false
+ )
+ MspMfViewHolder(seMspBinding)
+ }
+
+ else -> InvalidViewHolder(View(context))
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val labExperiment = labExperimentArrayList[position]
+ val serialOrder = processSerialOrder(labExperiment.serialOrder)
+ when (holder) {
+ is SspSfViewHolder -> {
+ holder.binding.serialOrder.text = serialOrder
+ val parts =
+ labExperiment.labExperimentSubParts[0].contentFiles[0].fileName.split("\\.".toRegex())
+ .dropLastWhile { it.isEmpty() }.toTypedArray()
+ if (parts.size >= 2) {
+ holder.binding.programTitle.text =
+ formatProgramName(parts[parts.size - 2])
+ }
+ }
+
+ is MspSfViewHolder -> {
+ holder.binding.serialOrder.text = serialOrder
+ val adapter = MultipleSubPartAdapter(
+ context,
+ labExperiment.labExperimentSubParts,
+ false,
+ itemClickHandler
+ )
+ holder.binding.subPartContainer.adapter = adapter
+ }
+
+ is SspMfViewHolder -> {
+ holder.binding.serialOrder.text = serialOrder
+ val adapter = MultipleFileAdapter(
+ context,
+ labExperiment.labExperimentSubParts[0].contentFiles,
+ itemClickHandler
+ )
+ holder.binding.filesContainer.adapter = adapter
+
+ }
+
+ is MspMfViewHolder -> {
+ holder.binding.serialOrder.text = serialOrder
+ val adapter = MultipleSubPartAdapter(
+ context,
+ labExperiment.labExperimentSubParts,
+ true,
+ itemClickHandler
+ )
+ holder.binding.subPartContainer.adapter = adapter
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return labExperimentArrayList.size
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ val experiment = labExperimentArrayList[position]
+ var contentFileLength = 1
+ val labExperimentSubParts = experiment.labExperimentSubParts
+ for (subPart in labExperimentSubParts) {
+ contentFileLength = contentFileLength.coerceAtLeast(subPart.contentFiles.size)
+ }
+ val labExperimentSubPartsSize = labExperimentSubParts.size
+ return if (labExperimentSubPartsSize == 1 && contentFileLength == 1)
+ VIEW_TYPE_SSP_SF
+ else if (labExperimentSubPartsSize > 1 && contentFileLength == 1)
+ VIEW_TYPE_MSP_SF
+ else if (labExperimentSubPartsSize == 1)
+ VIEW_TYPE_SSP_MF
+ else if (labExperimentSubPartsSize > 1)
+ VIEW_TYPE_MSP_MF
+ else VIEW_TYPE_INVALID
+ }
+
+ private fun processSerialOrder(order: String?): String {
+ if (order == null) {
+ return "#"
+ }
+ return try {
+ val i = order.toInt()
+ i.toString()
+ } catch (e: NumberFormatException) {
+ order
+ }
+ }
+
+ interface ItemClickHandler {
+ fun onContentFileClick(file: ContentFile)
+ }
+
+ internal class InvalidViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+ internal inner class SspSfViewHolder(var binding: LayoutCardSeSspSfBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ), View.OnClickListener {
+ init {
+ binding.root.setOnClickListener(this)
+ }
+
+ override fun onClick(view: View) {
+ itemClickHandler?.let {
+ val position = adapterPosition
+ val contentFile =
+ labExperimentArrayList[position].labExperimentSubParts[0].contentFiles[0]
+ it.onContentFileClick(contentFile)
+ }
+ }
+ }
+
+ internal inner class MspSfViewHolder(var binding: LayoutCardSeMspBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ) {
+ init {
+ binding.subPartContainer.layoutManager = LinearLayoutManager(context)
+ binding.subPartContainer.setHasFixedSize(true)
+ }
+ }
+
+ internal inner class SspMfViewHolder(var binding: LayoutCardSeSspMfBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ) {
+ init {
+ binding.filesContainer.layoutManager = LinearLayoutManager(context)
+ binding.filesContainer.setHasFixedSize(true)
+ }
+ }
+
+ internal inner class MspMfViewHolder(var binding: LayoutCardSeMspBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ) {
+ init {
+ binding.subPartContainer.layoutManager = LinearLayoutManager(context)
+ binding.subPartContainer.setHasFixedSize(true)
+ }
+ }
+
+ companion object {
+ private const val VIEW_TYPE_SSP_SF = 0
+ private const val VIEW_TYPE_MSP_SF = 1
+ private const val VIEW_TYPE_SSP_MF = 2
+ private const val VIEW_TYPE_MSP_MF = 3
+ private const val VIEW_TYPE_INVALID = -1
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleFileAdapter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleFileAdapter.kt
new file mode 100644
index 0000000..556f467
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleFileAdapter.kt
@@ -0,0 +1,54 @@
+package com.nagpal.shivam.vtucslab.adapters
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.nagpal.shivam.vtucslab.adapters.ContentAdapter.ItemClickHandler
+import com.nagpal.shivam.vtucslab.adapters.MultipleFileAdapter.ContentFileViewHolder
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleFilesWithoutSubPartsBinding
+import com.nagpal.shivam.vtucslab.models.ContentFile
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods.formatProgramName
+
+class MultipleFileAdapter(
+ private val context: Context,
+ private val contentFiles: List,
+ private val itemClickHandler: ItemClickHandler?
+) : RecyclerView.Adapter() {
+ override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ContentFileViewHolder {
+ val binding = LayoutCardSingleFilesWithoutSubPartsBinding.inflate(
+ LayoutInflater.from(context), viewGroup, false
+ )
+ return ContentFileViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(contentFileViewHolder: ContentFileViewHolder, i: Int) {
+ val parts =
+ contentFiles[i].fileName.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }
+ .toTypedArray()
+ if (parts.size >= 2) {
+ contentFileViewHolder.binding.programTitle.text =
+ formatProgramName(parts[parts.size - 2])
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return contentFiles.size
+ }
+
+ inner class ContentFileViewHolder(var binding: LayoutCardSingleFilesWithoutSubPartsBinding) :
+ RecyclerView.ViewHolder(binding.root), View.OnClickListener {
+ init {
+ binding.root.setOnClickListener(this)
+ }
+
+ override fun onClick(v: View) {
+ itemClickHandler?.let {
+ val position = adapterPosition
+ val file = contentFiles[position]
+ it.onContentFileClick(file)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleSubPartAdapter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleSubPartAdapter.kt
new file mode 100644
index 0000000..c92f026
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/MultipleSubPartAdapter.kt
@@ -0,0 +1,96 @@
+package com.nagpal.shivam.vtucslab.adapters
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.nagpal.shivam.vtucslab.adapters.ContentAdapter.ItemClickHandler
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleSubPartsWithFilesBinding
+import com.nagpal.shivam.vtucslab.databinding.LayoutCardSingleSubPartsWithoutFilesBinding
+import com.nagpal.shivam.vtucslab.models.LabExperimentSubPart
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods.formatProgramName
+
+class MultipleSubPartAdapter(
+ private val context: Context,
+ private val subParts: List,
+ private val containsMultipleFiles: Boolean,
+ private val itemClickHandler: ItemClickHandler?
+) : RecyclerView.Adapter() {
+ override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
+ return if (!containsMultipleFiles) {
+ val binding =
+ LayoutCardSingleSubPartsWithoutFilesBinding.inflate(
+ LayoutInflater.from(context),
+ viewGroup,
+ false
+ )
+ SubPartWithoutFilesViewHolder(binding)
+ } else {
+ val binding =
+ LayoutCardSingleSubPartsWithFilesBinding.inflate(
+ LayoutInflater.from(context),
+ viewGroup,
+ false
+ )
+ SubPartWithFilesViewHolder(binding)
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, i: Int) {
+ when (holder) {
+ is SubPartWithoutFilesViewHolder -> {
+ holder.binding.serialOrder.text = subParts[i].subSerialOrder
+ val parts = subParts[i].contentFiles[0].fileName.split("\\.".toRegex())
+ .dropLastWhile { it.isEmpty() }.toTypedArray()
+ if (parts.size >= 2) {
+ holder.binding.programTitle.text = formatProgramName(
+ parts[parts.size - 2]
+ )
+ }
+ }
+
+ is SubPartWithFilesViewHolder -> {
+ holder.binding.serialOrder.text = subParts[i].subSerialOrder
+ val adapter = MultipleFileAdapter(
+ context,
+ subParts[i].contentFiles,
+ itemClickHandler
+ )
+ holder.binding.filesContainer.adapter = adapter
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return subParts.size
+ }
+
+ internal inner class SubPartWithoutFilesViewHolder(var binding: LayoutCardSingleSubPartsWithoutFilesBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ), View.OnClickListener {
+ init {
+ binding.root.setOnClickListener(this)
+ }
+
+ override fun onClick(v: View) {
+ itemClickHandler?.let {
+ val position = adapterPosition
+ val contentFile = subParts[position].contentFiles[0]
+ it.onContentFileClick(contentFile)
+ }
+ }
+ }
+
+ internal inner class SubPartWithFilesViewHolder(var binding: LayoutCardSingleSubPartsWithFilesBinding) :
+ RecyclerView.ViewHolder(
+ binding.root
+ ) {
+ init {
+ binding.filesContainer.layoutManager = LinearLayoutManager(context)
+ binding.filesContainer.setHasFixedSize(true)
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/NavigationAdapter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/NavigationAdapter.kt
new file mode 100644
index 0000000..1a65a03
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/adapters/NavigationAdapter.kt
@@ -0,0 +1,73 @@
+package com.nagpal.shivam.vtucslab.adapters
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.adapters.NavigationAdapter.NavigationViewHolder
+import com.nagpal.shivam.vtucslab.models.Laboratory
+
+class NavigationAdapter(
+ private val context: Context,
+ private val laboratoryArrayList: ArrayList
+) : RecyclerView.Adapter() {
+ private var navigationAdapterItemClickHandler: NavigationAdapterItemClickHandler? = null
+ fun addAll(laboratories: List) {
+ val i = laboratoryArrayList.size
+ laboratoryArrayList.addAll(laboratories)
+ notifyItemRangeInserted(i, laboratories.size)
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun clear() {
+ laboratoryArrayList.clear()
+ notifyDataSetChanged()
+ }
+
+ fun setNavigationAdapterItemClickHandler(navigationAdapterItemClickHandler: NavigationAdapterItemClickHandler?) {
+ this.navigationAdapterItemClickHandler = navigationAdapterItemClickHandler
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NavigationViewHolder {
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.layout_card_repository, parent, false)
+ return NavigationViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: NavigationViewHolder, position: Int) {
+ val laboratory = laboratoryArrayList[position]
+ val fileName = laboratory.fileName
+ val parts = fileName.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ laboratory.title = parts[0].replace('_', ' ')
+ holder.textView.text = laboratory.title
+ }
+
+ override fun getItemCount(): Int {
+ return laboratoryArrayList.size
+ }
+
+ interface NavigationAdapterItemClickHandler {
+ fun onNavigationAdapterItemClick(laboratory: Laboratory, i: Int)
+ }
+
+ inner class NavigationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
+ View.OnClickListener {
+ var textView: TextView
+
+ init {
+ textView = itemView.findViewById(R.id.text_view_layout)
+ itemView.setOnClickListener(this)
+ }
+
+ override fun onClick(view: View) {
+ navigationAdapterItemClickHandler?.let {
+ val i = adapterPosition
+ it.onNavigationAdapterItemClick(laboratoryArrayList[i], i)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/core/ErrorType.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/core/ErrorType.kt
new file mode 100644
index 0000000..5cee513
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/core/ErrorType.kt
@@ -0,0 +1,6 @@
+package com.nagpal.shivam.vtucslab.core
+
+enum class ErrorType {
+ NoActiveInternetConnection,
+ SomeErrorOccurred,
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/core/Resource.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/core/Resource.kt
new file mode 100644
index 0000000..8e1523e
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/core/Resource.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.core
+
+sealed class Resource {
+ class Loading(val data: D? = null) : Resource()
+ class Success(val data: D?) : Resource()
+ class Error(val error: E) : Resource()
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessage.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessage.kt
new file mode 100644
index 0000000..2d09290
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessage.kt
@@ -0,0 +1,3 @@
+package com.nagpal.shivam.vtucslab.core
+
+data class UIMessage(val messageType: UIMessageType, val args: List = emptyList())
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessageType.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessageType.kt
new file mode 100644
index 0000000..d717723
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/core/UIMessageType.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.core
+
+enum class UIMessageType {
+ NoActiveInternetConnectionDetailed,
+ NoActiveInternetConnection,
+ SomeErrorOccurred,
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/AppDatabase.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/AppDatabase.kt
new file mode 100644
index 0000000..2a39579
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/AppDatabase.kt
@@ -0,0 +1,11 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+
+@Database(entities = [LabResponse::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class AppDatabase : RoomDatabase() {
+ abstract fun labResponseDao(): LabResponseDao
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/Converters.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/Converters.kt
new file mode 100644
index 0000000..996822a
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/Converters.kt
@@ -0,0 +1,16 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+import androidx.room.TypeConverter
+import java.util.Date
+
+class Converters {
+ @TypeConverter
+ fun dateFromTimestamp(value: Long?): Date? {
+ return value?.let { Date(it) }
+ }
+
+ @TypeConverter
+ fun dateToTimestamp(date: Date?): Long? {
+ return date?.time
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/DBConstants.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/DBConstants.kt
new file mode 100644
index 0000000..57c0b6e
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/DBConstants.kt
@@ -0,0 +1,12 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+object Tables {
+ const val LAB_RESPONSE = "lab_response"
+}
+
+object LabResponseAttributes {
+ const val URL = "url"
+ const val RESPONSE = "response"
+ const val RESPONSE_TYPE = "response_type"
+ const val FETCHED_AT = "fetched_at"
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponse.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponse.kt
new file mode 100644
index 0000000..f0a9ea4
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponse.kt
@@ -0,0 +1,19 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.FETCHED_AT
+import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.RESPONSE
+import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.RESPONSE_TYPE
+import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.URL
+import com.nagpal.shivam.vtucslab.data.local.Tables.LAB_RESPONSE
+import java.util.*
+
+@Entity(LAB_RESPONSE)
+data class LabResponse(
+ @PrimaryKey @ColumnInfo(name = URL) val url: String,
+ @ColumnInfo(name = RESPONSE) val response: String,
+ @ColumnInfo(name = RESPONSE_TYPE) val responseType: LabResponseType,
+ @ColumnInfo(name = FETCHED_AT) val fetchedAt: Date,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseDao.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseDao.kt
new file mode 100644
index 0000000..f8d7b74
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseDao.kt
@@ -0,0 +1,16 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+import androidx.room.Dao
+import androidx.room.Query
+import androidx.room.Upsert
+import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.URL
+import com.nagpal.shivam.vtucslab.data.local.Tables.LAB_RESPONSE
+
+@Dao
+interface LabResponseDao {
+ @Upsert
+ fun upsert(labResponse: LabResponse)
+
+ @Query("SELECT * FROM $LAB_RESPONSE where $URL = :url")
+ fun findByUrl(url: String): LabResponse?
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseType.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseType.kt
new file mode 100644
index 0000000..90347b9
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/data/local/LabResponseType.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.data.local
+
+enum class LabResponseType {
+ LABORATORY,
+ EXPERIMENT,
+ CONTENT,
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/ContentFile.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/ContentFile.kt
new file mode 100644
index 0000000..664538a
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/ContentFile.kt
@@ -0,0 +1,5 @@
+package com.nagpal.shivam.vtucslab.models
+
+data class ContentFile(
+ var fileName: String,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperiment.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperiment.kt
new file mode 100644
index 0000000..92b3ccb
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperiment.kt
@@ -0,0 +1,6 @@
+package com.nagpal.shivam.vtucslab.models
+
+data class LabExperiment(
+ var serialOrder: String,
+ var labExperimentSubParts: List,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperimentSubPart.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperimentSubPart.kt
new file mode 100644
index 0000000..80d2c34
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LabExperimentSubPart.kt
@@ -0,0 +1,6 @@
+package com.nagpal.shivam.vtucslab.models
+
+data class LabExperimentSubPart(
+ var subSerialOrder: String?,
+ var contentFiles: List,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/Laboratory.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/Laboratory.kt
new file mode 100644
index 0000000..3c8cc29
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/Laboratory.kt
@@ -0,0 +1,6 @@
+package com.nagpal.shivam.vtucslab.models
+
+data class Laboratory(
+ var title: String?,
+ var fileName: String,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryExperimentResponse.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryExperimentResponse.kt
new file mode 100644
index 0000000..3611981
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryExperimentResponse.kt
@@ -0,0 +1,14 @@
+package com.nagpal.shivam.vtucslab.models
+
+import com.fasterxml.jackson.annotation.JsonAlias
+import com.nagpal.shivam.vtucslab.utilities.Constants
+
+data class LaboratoryExperimentResponse(
+ @field:JsonAlias(Constants.GITHUB_RAW_CONTENT) val githubRawContent: String,
+ val organization: String,
+ val repository: String,
+ val branch: String,
+ val isValid: Boolean = true,
+ val labExperiments: List,
+ val invalidationMessage: String? = null,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryResponse.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryResponse.kt
new file mode 100644
index 0000000..f41cc2e
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/models/LaboratoryResponse.kt
@@ -0,0 +1,14 @@
+package com.nagpal.shivam.vtucslab.models
+
+import com.fasterxml.jackson.annotation.JsonAlias
+import com.nagpal.shivam.vtucslab.utilities.Constants
+
+data class LaboratoryResponse(
+ @field:JsonAlias(Constants.GITHUB_RAW_CONTENT) val githubRawContent: String,
+ val organization: String,
+ val repository: String,
+ val branch: String,
+ val isValid: Boolean = true,
+ val laboratories: List,
+ val invalidationMessage: String? = null,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepository.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepository.kt
new file mode 100644
index 0000000..9e4c62c
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepository.kt
@@ -0,0 +1,24 @@
+package com.nagpal.shivam.vtucslab.repositories
+
+import com.nagpal.shivam.vtucslab.core.ErrorType
+import com.nagpal.shivam.vtucslab.core.Resource
+import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
+import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
+import kotlinx.coroutines.flow.Flow
+
+interface VtuCsLabRepository {
+ fun fetchLaboratories(
+ url: String,
+ forceRefresh: Boolean,
+ ): Flow>
+
+ fun fetchExperiments(
+ url: String,
+ forceRefresh: Boolean,
+ ): Flow>
+
+ fun fetchContent(
+ url: String,
+ forceRefresh: Boolean,
+ ): Flow>
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepositoryImpl.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepositoryImpl.kt
new file mode 100644
index 0000000..4017951
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/repositories/VtuCsLabRepositoryImpl.kt
@@ -0,0 +1,172 @@
+package com.nagpal.shivam.vtucslab.repositories
+
+import android.app.Application
+import com.fasterxml.jackson.core.JsonParseException
+import com.fasterxml.jackson.databind.json.JsonMapper
+import com.nagpal.shivam.vtucslab.core.ErrorType
+import com.nagpal.shivam.vtucslab.core.Resource
+import com.nagpal.shivam.vtucslab.data.local.LabResponse
+import com.nagpal.shivam.vtucslab.data.local.LabResponseDao
+import com.nagpal.shivam.vtucslab.data.local.LabResponseType
+import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
+import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult
+import com.nagpal.shivam.vtucslab.services.VtuCsLabService
+import com.nagpal.shivam.vtucslab.utilities.Configurations
+import com.nagpal.shivam.vtucslab.utilities.NetworkUtils
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.flow
+import java.util.Date
+
+private val LOG_TAG: String = VtuCsLabRepositoryImpl::class.java.name
+
+class VtuCsLabRepositoryImpl(
+ private val application: Application,
+ private val vtuCsLabService: VtuCsLabService,
+ private val labResponseDao: LabResponseDao,
+ private val jsonMapper: JsonMapper,
+) : VtuCsLabRepository {
+ override fun fetchLaboratories(
+ url: String,
+ forceRefresh: Boolean
+ ): Flow> =
+ flow {
+ fetch(
+ flow = this,
+ url,
+ LabResponseType.LABORATORY,
+ vtuCsLabService::getLaboratoryResponse,
+ { data -> jsonMapper.writeValueAsString(data) },
+ { stringContent ->
+ jsonMapper.readValue(
+ stringContent,
+ LaboratoryResponse::class.java
+ )
+ },
+ forceRefresh,
+ )
+ }
+
+ override fun fetchExperiments(
+ url: String,
+ forceRefresh: Boolean
+ ): Flow> =
+ flow {
+ fetch(
+ flow = this,
+ url,
+ LabResponseType.EXPERIMENT,
+ vtuCsLabService::getLaboratoryExperimentsResponse,
+ { data -> jsonMapper.writeValueAsString(data) },
+ { stringContent ->
+ jsonMapper.readValue(
+ stringContent,
+ LaboratoryExperimentResponse::class.java
+ )
+ },
+ forceRefresh,
+ )
+ }
+
+ override fun fetchContent(
+ url: String,
+ forceRefresh: Boolean
+ ): Flow> = flow {
+ fetch(
+ flow = this,
+ url,
+ LabResponseType.CONTENT,
+ vtuCsLabService::fetchRawResponse,
+ { stringContent -> stringContent },
+ { stringContent -> stringContent },
+ forceRefresh,
+ )
+ }
+
+ private suspend fun fetch(
+ flow: FlowCollector>,
+ url: String,
+ labResponseType: LabResponseType,
+ fetchFromNetwork: suspend (String) -> ApiResult,
+ encodeToString: (D) -> String,
+ decodeFromString: (String) -> D,
+ forceRefresh: Boolean,
+ ) {
+ flow.emit(Resource.Loading())
+
+ var foundInDB = false
+ if (!forceRefresh) {
+ val labResponse = labResponseDao.findByUrl(url)
+ labResponse?.let {
+ try {
+ flow.emit(Resource.Success(decodeFromString.invoke(it.response)))
+ foundInDB = true
+ if (it.fetchedAt.after(
+ StaticMethods.getCurrentDateMinusSeconds(Configurations.RESPONSE_FRESHNESS_TIME)
+ )
+ ) {
+ return
+ }
+ } catch (_: JsonParseException) {
+ }
+ }
+ }
+
+ if (!NetworkUtils.isNetworkConnected(application)) {
+ emitNetworkErrors(
+ flow,
+ forceRefresh,
+ foundInDB,
+ Resource.Error(ErrorType.NoActiveInternetConnection),
+ )
+ return
+ }
+ when (val apiResult = fetchFromNetwork.invoke(url)) {
+ is ApiResult.ApiSuccess -> {
+ val data = apiResult.data
+ labResponseDao.upsert(
+ LabResponse(
+ url,
+ encodeToString(data),
+ labResponseType,
+ Date(),
+ )
+ )
+ flow.emit(Resource.Success(data))
+ }
+
+ is ApiResult.ApiError -> {
+ StaticMethods.logNetworkResultError(LOG_TAG, url, apiResult.code, apiResult.message)
+ emitNetworkErrors(
+ flow,
+ forceRefresh,
+ foundInDB,
+ Resource.Error(ErrorType.SomeErrorOccurred),
+ )
+ }
+
+ is ApiResult.ApiException -> {
+ StaticMethods.logNetworkResultException(LOG_TAG, url, apiResult.throwable)
+ emitNetworkErrors(
+ flow,
+ forceRefresh,
+ foundInDB,
+ Resource.Error(ErrorType.SomeErrorOccurred),
+ )
+ }
+ }
+ }
+
+ private suspend fun emitNetworkErrors(
+ flow: FlowCollector>,
+ forceRefresh: Boolean,
+ foundInDB: Boolean,
+ errorResource: Resource.Error
+ ) {
+ if (forceRefresh || !foundInDB) {
+ flow.emit(errorResource)
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResult.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResult.kt
new file mode 100644
index 0000000..641baf2
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResult.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.retrofit
+
+sealed class ApiResult {
+ class ApiSuccess(val data: T) : ApiResult()
+ class ApiError(val code: Int, val message: String?) : ApiResult()
+ class ApiException(val throwable: Throwable) : ApiResult()
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCall.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCall.kt
new file mode 100644
index 0000000..27cd0c8
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCall.kt
@@ -0,0 +1,39 @@
+package com.nagpal.shivam.vtucslab.retrofit
+
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult.ApiException
+import okhttp3.Request
+import okio.Timeout
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class ApiResultCall(private val proxy: Call) : Call> {
+ override fun enqueue(callback: Callback>) {
+ proxy.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ val apiResult = handleApiResult { response }
+ callback.onResponse(this@ApiResultCall, Response.success(apiResult))
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ val apiResult = ApiException(t)
+ callback.onResponse(this@ApiResultCall, Response.success(apiResult))
+ }
+ })
+ }
+
+ override fun execute(): Response> = throw NotImplementedError()
+
+ override fun clone(): Call> = ApiResultCall(proxy.clone())
+
+ override fun isExecuted(): Boolean = proxy.isExecuted
+
+ override fun cancel() = proxy.cancel()
+
+ override fun isCanceled(): Boolean = proxy.isCanceled
+
+ override fun request(): Request = proxy.request()
+
+ override fun timeout(): Timeout = proxy.timeout()
+
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapter.kt
new file mode 100644
index 0000000..5778538
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapter.kt
@@ -0,0 +1,15 @@
+package com.nagpal.shivam.vtucslab.retrofit
+
+import retrofit2.Call
+import retrofit2.CallAdapter
+import java.lang.reflect.Type
+
+class ApiResultCallAdapter(private val resultType: Type) :
+ CallAdapter>> {
+ override fun responseType(): Type = resultType
+
+ override fun adapt(call: Call): Call> {
+ return ApiResultCall(call)
+ }
+
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapterFactory.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapterFactory.kt
new file mode 100644
index 0000000..0bbe9fc
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/ApiResultCallAdapterFactory.kt
@@ -0,0 +1,31 @@
+package com.nagpal.shivam.vtucslab.retrofit
+
+import retrofit2.Call
+import retrofit2.CallAdapter
+import retrofit2.Retrofit
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+class ApiResultCallAdapterFactory private constructor() : CallAdapter.Factory() {
+ override fun get(
+ returnType: Type,
+ annotations: Array,
+ retrofit: Retrofit
+ ): CallAdapter<*, *>? {
+ if (getRawType(returnType) != Call::class.java) {
+ return null
+ }
+
+ val callType = getParameterUpperBound(0, returnType as ParameterizedType)
+ if (getRawType(callType) != ApiResult::class.java) {
+ return null
+ }
+
+ val resultType = getParameterUpperBound(0, callType as ParameterizedType)
+ return ApiResultCallAdapter(resultType)
+ }
+
+ companion object {
+ fun create(): ApiResultCallAdapterFactory = ApiResultCallAdapterFactory()
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/RetrofitConfigurations.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/RetrofitConfigurations.kt
new file mode 100644
index 0000000..ff865c4
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/retrofit/RetrofitConfigurations.kt
@@ -0,0 +1,37 @@
+package com.nagpal.shivam.vtucslab.retrofit
+
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult.ApiError
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult.ApiException
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult.ApiSuccess
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods
+import retrofit2.HttpException
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.jackson.JacksonConverterFactory
+import retrofit2.converter.scalars.ScalarsConverterFactory
+
+
+fun getRetrofitBuilder(): Retrofit.Builder {
+ return Retrofit.Builder()
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(JacksonConverterFactory.create(StaticMethods.jsonMapper))
+ .addCallAdapterFactory(ApiResultCallAdapterFactory.create())
+}
+
+fun handleApiResult(
+ execute: () -> Response
+): ApiResult {
+ return try {
+ val response = execute()
+ val body = response.body()
+ if (response.isSuccessful && body != null) {
+ ApiSuccess(body)
+ } else {
+ ApiError(code = response.code(), message = response.message())
+ }
+ } catch (e: HttpException) {
+ ApiError(code = e.code(), message = e.message())
+ } catch (e: Throwable) {
+ ApiException(e)
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/ContentState.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/ContentState.kt
new file mode 100644
index 0000000..5b3fbce
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/ContentState.kt
@@ -0,0 +1,11 @@
+package com.nagpal.shivam.vtucslab.screens
+
+import com.nagpal.shivam.vtucslab.core.UIMessage
+
+data class ContentState(
+ val stage: String,
+ val data: T? = null,
+ val errorMessage: UIMessage? = null,
+ val toast: UIMessage? = null,
+ val baseUrl: String? = null,
+)
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/EventEmitter.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/EventEmitter.kt
new file mode 100644
index 0000000..8f784a9
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/EventEmitter.kt
@@ -0,0 +1,5 @@
+package com.nagpal.shivam.vtucslab.screens
+
+interface EventEmitter {
+ fun onEvent(event: T)
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/UiEvent.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/UiEvent.kt
new file mode 100644
index 0000000..7197d72
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/UiEvent.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.screens
+
+sealed class UiEvent {
+ class LoadContent(val url: String) : UiEvent()
+ class RefreshContent(val url: String) : UiEvent()
+ object ResetToast : UiEvent()
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/Utils.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/Utils.kt
new file mode 100644
index 0000000..f0bf5ad
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/Utils.kt
@@ -0,0 +1,111 @@
+package com.nagpal.shivam.vtucslab.screens
+
+import android.content.Context
+import android.widget.Toast
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.core.ErrorType
+import com.nagpal.shivam.vtucslab.core.Resource
+import com.nagpal.shivam.vtucslab.core.UIMessage
+import com.nagpal.shivam.vtucslab.core.UIMessageType
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+object Utils {
+ fun loadContent(
+ uiStateFlow: MutableStateFlow>,
+ fetchJob: Job?,
+ viewModelScope: CoroutineScope,
+ fetchExecutable: (String, Boolean) -> Flow>,
+ getBaseUrl: (T) -> String?,
+ url: String,
+ forceRefresh: Boolean,
+ ): Job? {
+ if (!forceRefresh && uiStateFlow.value.stage == Stages.SUCCEEDED) {
+ return fetchJob
+ }
+
+ fetchJob?.cancel()
+ return viewModelScope.launch(Dispatchers.IO) {
+ fetchExecutable.invoke(url, forceRefresh)
+ .onEach { resource ->
+ when (resource) {
+ is Resource.Loading -> {
+ uiStateFlow.update {
+ uiStateFlow.value.copy(stage = Stages.LOADING)
+ }
+ }
+
+ is Resource.Success -> {
+ uiStateFlow.update {
+ ContentState(
+ Stages.SUCCEEDED,
+ data = resource.data,
+ baseUrl = getBaseUrl(resource.data!!),
+ )
+ }
+ }
+
+ is Resource.Error -> {
+ uiStateFlow.update {
+ val uiMessage: UIMessage = when (resource.error) {
+ ErrorType.NoActiveInternetConnection -> if (forceRefresh) UIMessage(
+ UIMessageType.NoActiveInternetConnection
+ ) else UIMessage(UIMessageType.NoActiveInternetConnectionDetailed)
+
+ ErrorType.SomeErrorOccurred -> UIMessage(UIMessageType.SomeErrorOccurred)
+ }
+ if (forceRefresh) {
+ uiStateFlow.value.copy(
+ stage = if (uiStateFlow.value.data != null) Stages.SUCCEEDED else Stages.FAILED,
+ toast = uiMessage,
+ )
+ } else {
+ ContentState(
+ stage = Stages.FAILED,
+ errorMessage = uiMessage,
+ )
+ }
+
+ }
+ }
+ }
+ }.launchIn(this)
+ }
+ }
+
+ fun UIMessage.asString(context: Context): String {
+ return when (this.messageType) {
+ UIMessageType.NoActiveInternetConnectionDetailed -> context.getString(R.string.no_internet_connection_detailed)
+ UIMessageType.SomeErrorOccurred -> context.getString(R.string.error_occurred)
+ UIMessageType.NoActiveInternetConnection -> context.getString(R.string.no_internet_connection)
+ }
+ }
+
+ fun showToast(
+ context: Context,
+ toast: Toast?,
+ toastUIMessage: UIMessage?,
+ eventEmitter: EventEmitter,
+ event: T
+ ): Toast? {
+ return toastUIMessage?.let { uiMessage ->
+ toast?.cancel()
+ val newToast = Toast.makeText(
+ context,
+ uiMessage.asString(context),
+ Toast.LENGTH_LONG
+ )
+ newToast.show()
+ eventEmitter.onEvent(event)
+ newToast
+ } ?: toast
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayFragment.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayFragment.kt
new file mode 100644
index 0000000..2d01a68
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayFragment.kt
@@ -0,0 +1,157 @@
+package com.nagpal.shivam.vtucslab.screens.display
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.view.MenuProvider
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.navArgs
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.databinding.FragmentDisplayBinding
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.screens.Utils.asString
+import com.nagpal.shivam.vtucslab.utilities.Constants
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import kotlinx.coroutines.launch
+
+
+class DisplayFragment : Fragment() {
+
+ private var _binding: FragmentDisplayBinding? = null
+ private val binding get() = _binding!!
+ private val displayFragmentArgs by navArgs()
+ private val viewModel: DisplayViewModel by viewModels { DisplayViewModel.Factory }
+ private var toast: Toast? = null
+
+ private val url: String by lazy {
+ return@lazy "${displayFragmentArgs.baseUrl}/${displayFragmentArgs.fileName}"
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentDisplayBinding.inflate(inflater, container, false)
+ setupMenuProvider()
+ setupViews()
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect {
+ binding.emptyTextView.visibility = View.GONE
+ toast = Utils.showToast(
+ requireContext(),
+ toast,
+ it.toast,
+ viewModel,
+ UiEvent.ResetToast
+ )
+
+ if (it.stage != Stages.LOADING) {
+ binding.swipeRefresh.isRefreshing = false
+ }
+
+ when (it.stage) {
+ Stages.LOADING -> {
+ binding.swipeRefresh.isRefreshing = true
+ }
+
+ Stages.SUCCEEDED -> {
+ binding.displayTextView.text = it.data
+ requireActivity().invalidateOptionsMenu()
+ Handler(Looper.getMainLooper()).postDelayed({
+ binding.horizontalScroll.scrollX = viewModel.scrollX
+ binding.verticalScroll.scrollY = viewModel.scrollY
+ }, 500)
+ }
+
+ Stages.FAILED -> {
+ it.errorMessage?.let { uiMessage ->
+ showErrorMessage(uiMessage.asString(requireContext()))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return binding.root
+ }
+
+ private fun setupViews() {
+ binding.swipeRefresh.setOnRefreshListener {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ }
+ }
+
+ private fun showErrorMessage(message: String) {
+ binding.emptyTextView.visibility = View.VISIBLE
+ binding.emptyTextView.text = message
+ }
+
+ private fun setupMenuProvider() {
+ requireActivity().addMenuProvider(object : MenuProvider {
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_display_fragment, menu)
+ if (viewModel.uiState.value.stage == Stages.SUCCEEDED) {
+ menu.findItem(R.id.menu_item_copy_display_activity).isEnabled = true
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ return when (menuItem.itemId) {
+ R.id.menu_item_refresh -> {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ return true
+ }
+
+ R.id.menu_item_copy_display_activity -> {
+
+ val clipboard =
+ requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clipData = ClipData(
+ ClipData.newPlainText(
+ Constants.LABEL_CODE,
+ viewModel.uiState.value.data
+ )
+ )
+ clipboard.setPrimaryClip(clipData)
+ Toast.makeText(
+ requireContext(),
+ getString(R.string.code_copied_to_clipboard),
+ Toast.LENGTH_SHORT
+ ).show()
+ return true
+ }
+
+ else -> false
+ }
+ }
+ }, viewLifecycleOwner)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ viewModel.onEvent(UiEvent.LoadContent(url))
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ viewModel.scrollX = binding.horizontalScroll.scrollX
+ viewModel.scrollY = binding.verticalScroll.scrollY
+ super.onSaveInstanceState(outState)
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayViewModel.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayViewModel.kt
new file mode 100644
index 0000000..59ea723
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/display/DisplayViewModel.kt
@@ -0,0 +1,73 @@
+package com.nagpal.shivam.vtucslab.screens.display
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.initializer
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.nagpal.shivam.vtucslab.VTUCSLabApplication
+import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepository
+import com.nagpal.shivam.vtucslab.screens.ContentState
+import com.nagpal.shivam.vtucslab.screens.EventEmitter
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class DisplayViewModel(
+ application: Application,
+ private val vtuCsLabRepository: VtuCsLabRepository
+) : AndroidViewModel(application), EventEmitter {
+ var scrollX = 0
+ var scrollY = 0
+ private val initialState = ContentState(Stages.LOADING)
+
+ private val _uiState = MutableStateFlow(initialState)
+ val uiState = _uiState.asStateFlow()
+ private var fetchJob: Job? = null
+
+ override fun onEvent(event: UiEvent) {
+ when (event) {
+ is UiEvent.LoadContent -> {
+ loadContent(event.url)
+ }
+
+ is UiEvent.RefreshContent -> {
+ loadContent(event.url, true)
+ }
+
+ UiEvent.ResetToast -> {
+ _uiState.update { _uiState.value.copy(toast = null) }
+ }
+ }
+ }
+
+ private fun loadContent(url: String, forceRefresh: Boolean = false) {
+ fetchJob = Utils.loadContent(
+ _uiState,
+ fetchJob,
+ viewModelScope,
+ { urlArg, forceRefreshArg -> vtuCsLabRepository.fetchContent(urlArg, forceRefreshArg) },
+ { null },
+ url,
+ forceRefresh,
+ )
+ }
+
+ companion object {
+ val Factory: ViewModelProvider.Factory = viewModelFactory {
+ initializer {
+ val vtuCsLabApplication = this[APPLICATION_KEY] as VTUCSLabApplication
+ DisplayViewModel(
+ vtuCsLabApplication,
+ vtuCsLabApplication.vtuCsLabRepository
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramFragment.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramFragment.kt
new file mode 100644
index 0000000..ae78d69
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramFragment.kt
@@ -0,0 +1,158 @@
+package com.nagpal.shivam.vtucslab.screens.programs
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.view.MenuProvider
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.adapters.ContentAdapter
+import com.nagpal.shivam.vtucslab.databinding.FragmentProgramBinding
+import com.nagpal.shivam.vtucslab.models.ContentFile
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.screens.Utils.asString
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import kotlinx.coroutines.launch
+
+
+class ProgramFragment : Fragment() {
+ private var _binding: FragmentProgramBinding? = null
+ private val binding get() = _binding!!
+
+ private val viewModel: ProgramViewModel by viewModels { ProgramViewModel.Factory }
+ private lateinit var contentAdapter: ContentAdapter
+
+ private val programFragmentArgs by navArgs()
+ private var toast: Toast? = null
+
+ private val url: String by lazy {
+ return@lazy "${programFragmentArgs.baseUrl}/${programFragmentArgs.fileName}"
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentProgramBinding.inflate(inflater, container, false)
+ setupMenuProvider()
+ setupViews()
+ setupRepositoryAdapter()
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect {
+ binding.emptyTextView.visibility = View.GONE
+ toast = Utils.showToast(
+ requireContext(),
+ toast,
+ it.toast,
+ viewModel,
+ UiEvent.ResetToast
+ )
+
+ if (it.stage != Stages.LOADING) {
+ binding.swipeRefresh.isRefreshing = false
+ }
+
+ when (it.stage) {
+ Stages.LOADING -> {
+ binding.swipeRefresh.isRefreshing = true
+ }
+
+ Stages.SUCCEEDED -> {
+ if (it.data!!.isValid) {
+ contentAdapter.clear()
+ contentAdapter.addAll(it.data.labExperiments)
+ } else {
+ // TODO: Handle this logic in Data Layer
+ showErrorMessage(it.data.invalidationMessage)
+ }
+ }
+
+ Stages.FAILED -> {
+ it.errorMessage?.let { uiMessage ->
+ showErrorMessage(uiMessage.asString(requireContext()))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return binding.root
+ }
+
+ // TODO: Duplicate: Move to a static method
+ private fun showErrorMessage(message: String?) {
+ binding.emptyTextView.visibility = View.VISIBLE
+ binding.emptyTextView.text = message
+ }
+
+ private fun setupMenuProvider() {
+ requireActivity().addMenuProvider(object : MenuProvider {
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_program_fragment, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ return when (menuItem.itemId) {
+ R.id.menu_item_refresh -> {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ true
+ }
+
+ else -> false
+ }
+ }
+ }, viewLifecycleOwner)
+ }
+
+ private fun setupRepositoryAdapter() {
+ contentAdapter = ContentAdapter(requireContext(), ArrayList())
+ contentAdapter.setItemClickHandler(object : ContentAdapter.ItemClickHandler {
+ override fun onContentFileClick(file: ContentFile) {
+ val actionProgramFragmentToDisplayFragment =
+ ProgramFragmentDirections.actionProgramFragmentToDisplayFragment(
+ viewModel.uiState.value.baseUrl!!,
+ file.fileName,
+ file.fileName
+ )
+ findNavController().navigate(actionProgramFragmentToDisplayFragment)
+ }
+
+ })
+ binding.recyclerView.adapter = contentAdapter
+ }
+
+ private fun setupViews() {
+ binding.recyclerView.layoutManager =
+ LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
+ binding.recyclerView.setHasFixedSize(true)
+ binding.swipeRefresh.setOnRefreshListener {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ viewModel.onEvent(UiEvent.LoadContent(url))
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramViewModel.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramViewModel.kt
new file mode 100644
index 0000000..e6b32c3
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/programs/ProgramViewModel.kt
@@ -0,0 +1,80 @@
+package com.nagpal.shivam.vtucslab.screens.programs
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.initializer
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.nagpal.shivam.vtucslab.VTUCSLabApplication
+import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
+import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepository
+import com.nagpal.shivam.vtucslab.screens.ContentState
+import com.nagpal.shivam.vtucslab.screens.EventEmitter
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class ProgramViewModel(
+ application: Application,
+ private val vtuCsLabRepository: VtuCsLabRepository
+) : AndroidViewModel(application), EventEmitter {
+ private val initialState =
+ ContentState(Stages.LOADING)
+ private val _uiState = MutableStateFlow(initialState)
+ val uiState: StateFlow> = _uiState.asStateFlow()
+ private var fetchJob: Job? = null
+
+
+ override fun onEvent(event: UiEvent) {
+ when (event) {
+ is UiEvent.LoadContent -> {
+ loadContent(event.url)
+ }
+
+ is UiEvent.RefreshContent -> {
+ loadContent(event.url, true)
+ }
+
+ UiEvent.ResetToast -> {
+ _uiState.update { _uiState.value.copy(toast = null) }
+ }
+ }
+ }
+
+ private fun loadContent(url: String, forceRefresh: Boolean = false) {
+ fetchJob = Utils.loadContent(
+ _uiState,
+ fetchJob,
+ viewModelScope,
+ { urlArg, forceRefreshArg ->
+ vtuCsLabRepository.fetchExperiments(
+ urlArg,
+ forceRefreshArg
+ )
+ },
+ { StaticMethods.getBaseURL(it) },
+ url,
+ forceRefresh,
+ )
+ }
+
+ companion object {
+ val Factory: ViewModelProvider.Factory = viewModelFactory {
+ initializer {
+ val vtuCsLabApplication = this[APPLICATION_KEY] as VTUCSLabApplication
+ ProgramViewModel(
+ vtuCsLabApplication,
+ vtuCsLabApplication.vtuCsLabRepository
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryFragment.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryFragment.kt
new file mode 100644
index 0000000..9491fdf
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryFragment.kt
@@ -0,0 +1,162 @@
+package com.nagpal.shivam.vtucslab.screens.repository
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.view.MenuProvider
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.nagpal.shivam.vtucslab.R
+import com.nagpal.shivam.vtucslab.adapters.NavigationAdapter
+import com.nagpal.shivam.vtucslab.databinding.FragmentRepositoryBinding
+import com.nagpal.shivam.vtucslab.models.Laboratory
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.screens.Utils.asString
+import com.nagpal.shivam.vtucslab.utilities.Constants
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import kotlinx.coroutines.launch
+
+class RepositoryFragment : Fragment() {
+ private var _binding: FragmentRepositoryBinding? = null
+ private lateinit var navigationAdapter: NavigationAdapter
+
+ private val binding get() = _binding!!
+ private val viewModel: RepositoryViewModel by viewModels { RepositoryViewModel.Factory }
+ private val url = Constants.INDEX_REPOSITORY_URL
+
+ private var toast: Toast? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentRepositoryBinding.inflate(inflater, container, false)
+ setupMenuProvider()
+ setupViews()
+ setupRepositoryAdapter()
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect {
+ binding.emptyTextView.visibility = View.GONE
+ toast = Utils.showToast(
+ requireContext(),
+ toast,
+ it.toast,
+ viewModel,
+ UiEvent.ResetToast
+ )
+
+ if (it.stage != Stages.LOADING) {
+ binding.swipeRefresh.isRefreshing = false
+ }
+
+ when (it.stage) {
+ Stages.LOADING -> {
+ binding.swipeRefresh.isRefreshing = true
+ }
+
+ Stages.SUCCEEDED -> {
+ if (it.data!!.isValid) {
+ navigationAdapter.clear()
+ navigationAdapter.addAll(it.data.laboratories)
+ } else {
+ // TODO: Handle this logic in Data Layer
+ showErrorMessage(it.data.invalidationMessage)
+ }
+ }
+
+ Stages.FAILED -> {
+ it.errorMessage?.let { uiMessage ->
+ showErrorMessage(uiMessage.asString(requireContext()))
+ }
+ }
+ }
+ }
+ }
+ }
+ return binding.root
+ }
+
+ // TODO: Duplicate: Move to a static method
+ private fun showErrorMessage(message: String?) {
+ binding.emptyTextView.visibility = View.VISIBLE
+ binding.emptyTextView.text = message
+ }
+
+ private fun setupViews() {
+ binding.repositoryRecyclerView.layoutManager =
+ LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
+ binding.repositoryRecyclerView.setHasFixedSize(true)
+ binding.swipeRefresh.setOnRefreshListener {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ viewModel.onEvent(UiEvent.LoadContent(url))
+ }
+
+ private fun setupRepositoryAdapter() {
+ navigationAdapter = NavigationAdapter(requireContext(), ArrayList())
+ navigationAdapter.setNavigationAdapterItemClickHandler(object :
+ NavigationAdapter.NavigationAdapterItemClickHandler {
+ override fun onNavigationAdapterItemClick(laboratory: Laboratory, i: Int) {
+ val actionRepositoryFragmentToProgramFragment =
+ RepositoryFragmentDirections.actionRepositoryFragmentToProgramFragment(
+ viewModel.uiState.value.baseUrl!!,
+ laboratory.fileName,
+ laboratory.title.orEmpty(),
+ )
+ findNavController().navigate(actionRepositoryFragmentToProgramFragment)
+ }
+
+ })
+ binding.repositoryRecyclerView.adapter = navigationAdapter
+ }
+
+ private fun setupMenuProvider() {
+ requireActivity().addMenuProvider(object : MenuProvider {
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_main_fragment, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ return when (menuItem.itemId) {
+ R.id.menu_item_refresh -> {
+ viewModel.onEvent(UiEvent.RefreshContent(url))
+ true
+ }
+
+ R.id.main_menu_item_privacy -> {
+ val intent = Intent(Intent.ACTION_VIEW)
+ intent.data = Uri.parse(Constants.PRIVACY_POLICY_LINK)
+ startActivity(intent)
+ true
+ }
+
+ else -> false
+ }
+ }
+ }, viewLifecycleOwner)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryViewModel.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryViewModel.kt
new file mode 100644
index 0000000..675e6e1
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/screens/repository/RepositoryViewModel.kt
@@ -0,0 +1,78 @@
+package com.nagpal.shivam.vtucslab.screens.repository
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.initializer
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.nagpal.shivam.vtucslab.VTUCSLabApplication
+import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
+import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepository
+import com.nagpal.shivam.vtucslab.screens.ContentState
+import com.nagpal.shivam.vtucslab.screens.EventEmitter
+import com.nagpal.shivam.vtucslab.screens.UiEvent
+import com.nagpal.shivam.vtucslab.screens.Utils
+import com.nagpal.shivam.vtucslab.utilities.Stages
+import com.nagpal.shivam.vtucslab.utilities.StaticMethods
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class RepositoryViewModel(
+ application: Application,
+ private val vtuCsLabRepository: VtuCsLabRepository
+) : AndroidViewModel(application), EventEmitter {
+ private val initialState = ContentState(Stages.LOADING)
+ private val _uiState = MutableStateFlow(initialState)
+ val uiState: StateFlow> = _uiState.asStateFlow()
+ private var fetchJob: Job? = null
+
+ override fun onEvent(event: UiEvent) {
+ when (event) {
+ is UiEvent.LoadContent -> {
+ loadContent(event.url)
+ }
+
+ is UiEvent.RefreshContent -> {
+ loadContent(event.url, true)
+ }
+
+ UiEvent.ResetToast -> {
+ _uiState.update { _uiState.value.copy(toast = null) }
+ }
+ }
+ }
+
+ private fun loadContent(url: String, forceRefresh: Boolean = false) {
+ fetchJob = Utils.loadContent(
+ _uiState,
+ fetchJob,
+ viewModelScope,
+ { urlArg, forceRefreshArg ->
+ vtuCsLabRepository.fetchLaboratories(
+ urlArg,
+ forceRefreshArg
+ )
+ },
+ { StaticMethods.getBaseURL(it) },
+ url,
+ forceRefresh,
+ )
+ }
+
+ companion object {
+ val Factory: ViewModelProvider.Factory = viewModelFactory {
+ initializer {
+ val vtuCsLabApplication = this[APPLICATION_KEY] as VTUCSLabApplication
+ RepositoryViewModel(
+ vtuCsLabApplication,
+ vtuCsLabApplication.vtuCsLabRepository
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/services/VtuCsLabService.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/services/VtuCsLabService.kt
new file mode 100644
index 0000000..8ddd4e3
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/services/VtuCsLabService.kt
@@ -0,0 +1,30 @@
+package com.nagpal.shivam.vtucslab.services
+
+import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
+import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
+import com.nagpal.shivam.vtucslab.retrofit.ApiResult
+import com.nagpal.shivam.vtucslab.retrofit.getRetrofitBuilder
+import com.nagpal.shivam.vtucslab.utilities.Constants
+import retrofit2.http.GET
+import retrofit2.http.Url
+
+interface VtuCsLabService {
+
+ @GET
+ suspend fun getLaboratoryResponse(@Url url: String): ApiResult
+
+ @GET
+ suspend fun getLaboratoryExperimentsResponse(@Url url: String): ApiResult
+
+ @GET
+ suspend fun fetchRawResponse(@Url url: String): ApiResult
+
+ companion object {
+ val instance: VtuCsLabService by lazy {
+ val retrofit = getRetrofitBuilder()
+ .baseUrl(Constants.GITHUB_RAW_BASE_URL)
+ .build()
+ return@lazy retrofit.create(VtuCsLabService::class.java)
+ }
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Configurations.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Configurations.kt
new file mode 100644
index 0000000..60f0e7a
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Configurations.kt
@@ -0,0 +1,5 @@
+package com.nagpal.shivam.vtucslab.utilities
+
+object Configurations {
+ const val RESPONSE_FRESHNESS_TIME = 3 * 60 * 60
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Constants.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Constants.kt
new file mode 100644
index 0000000..378bab7
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Constants.kt
@@ -0,0 +1,12 @@
+package com.nagpal.shivam.vtucslab.utilities
+
+object Constants {
+ const val GITHUB_RAW_BASE_URL = "https://raw.githubusercontent.com"
+ const val PRIVACY_POLICY_LINK =
+ "https://github.com/ShivamNagpal/Privacy_Policies/blob/master/VTU_CS_LAB_MANUAL.md"
+ const val INDEX_REPOSITORY_URL =
+ "https://raw.githubusercontent.com/vtucs/Index_v3/master/Index_v3.json"
+ const val LABEL_CODE = "Code"
+ const val GITHUB_RAW_CONTENT = "github_raw_content"
+ const val VTU_CS_LAB = "vtu_cs_lab"
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/NetworkUtils.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/NetworkUtils.kt
new file mode 100644
index 0000000..3a8f086
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/NetworkUtils.kt
@@ -0,0 +1,37 @@
+package com.nagpal.shivam.vtucslab.utilities
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
+
+object NetworkUtils {
+ fun isNetworkConnected(context: Context): Boolean {
+ val connectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val networkCapabilities = connectivityManager.activeNetwork ?: return false
+ val activeNetwork =
+ connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
+ return when {
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
+ else -> false
+ }
+ } else { //TODO: Remove this code post upgrading the minSdkVersion to 23 (https://stackoverflow.com/a/61152364)
+ connectivityManager.run {
+ connectivityManager.activeNetworkInfo?.run {
+ return when (type) {
+ ConnectivityManager.TYPE_WIFI -> true
+ ConnectivityManager.TYPE_MOBILE -> true
+ ConnectivityManager.TYPE_ETHERNET -> true
+ else -> false
+ }
+
+ }
+ }
+ }
+ return false
+ }
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Stages.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Stages.kt
new file mode 100644
index 0000000..29d2b07
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/Stages.kt
@@ -0,0 +1,7 @@
+package com.nagpal.shivam.vtucslab.utilities
+
+object Stages {
+ const val LOADING = "LOADING"
+ const val SUCCEEDED = "SUCCEEDED"
+ const val FAILED = "FAILED"
+}
diff --git a/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/StaticMethods.kt b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/StaticMethods.kt
new file mode 100644
index 0000000..3c629aa
--- /dev/null
+++ b/app/src/main/java/com/nagpal/shivam/vtucslab/utilities/StaticMethods.kt
@@ -0,0 +1,60 @@
+package com.nagpal.shivam.vtucslab.utilities
+
+import android.util.Log
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.json.JsonMapper
+import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
+import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
+import java.util.Calendar
+import java.util.Date
+
+object StaticMethods {
+
+ val jsonMapper: JsonMapper by lazy {
+ com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder()
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .build()
+ }
+
+ fun formatProgramName(programName: String): String {
+ return programName.replace('_', ' ')
+ }
+
+ fun getBaseURL(labResponse: LaboratoryResponse): String {
+ return "${labResponse.githubRawContent}/${labResponse.organization}/${labResponse.repository}/${labResponse.branch}"
+ }
+
+ fun getBaseURL(laboratoryExperimentResponse: LaboratoryExperimentResponse): String {
+ return "${laboratoryExperimentResponse.githubRawContent}/${laboratoryExperimentResponse.organization}/${laboratoryExperimentResponse.repository}/${laboratoryExperimentResponse.branch}"
+ }
+
+ fun logNetworkResultError(
+ logTag: String,
+ url: String,
+ code: Int,
+ message: String?
+ ) {
+ Log.e(
+ logTag,
+ "Call to $url resulted in non-success response. Status Code: $code. Message: $message"
+ )
+ }
+
+ fun logNetworkResultException(
+ logTag: String,
+ url: String,
+ throwable: Throwable
+ ) {
+ Log.e(
+ logTag,
+ "Call to $url failed with exception: ${throwable.javaClass.name}",
+ throwable
+ )
+ }
+
+ fun getCurrentDateMinusSeconds(seconds: Int): Date {
+ val calendar = Calendar.getInstance()
+ calendar.add(Calendar.SECOND, -seconds)
+ return calendar.time
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_action_arrow_back.png b/app/src/main/res/drawable-hdpi/ic_action_arrow_back.png
deleted file mode 100644
index e2ff536..0000000
Binary files a/app/src/main/res/drawable-hdpi/ic_action_arrow_back.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_arrow_back.png b/app/src/main/res/drawable-mdpi/ic_action_arrow_back.png
deleted file mode 100644
index 41de0b3..0000000
Binary files a/app/src/main/res/drawable-mdpi/ic_action_arrow_back.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_arrow_back.png b/app/src/main/res/drawable-xhdpi/ic_action_arrow_back.png
deleted file mode 100644
index 7b69282..0000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_action_arrow_back.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_arrow_back.png b/app/src/main/res/drawable-xxhdpi/ic_action_arrow_back.png
deleted file mode 100644
index 4d205b2..0000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_arrow_back.png and /dev/null differ
diff --git a/app/src/main/res/layout/activity_display.xml b/app/src/main/res/layout/activity_display.xml
deleted file mode 100644
index f351598..0000000
--- a/app/src/main/res/layout/activity_display.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4909472
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_program.xml b/app/src/main/res/layout/activity_program.xml
deleted file mode 100644
index 2d2cce2..0000000
--- a/app/src/main/res/layout/activity_program.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_repository.xml b/app/src/main/res/layout/activity_repository.xml
deleted file mode 100644
index 40f7bdf..0000000
--- a/app/src/main/res/layout/activity_repository.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/content_repository.xml b/app/src/main/res/layout/content_repository.xml
deleted file mode 100644
index 69b70d3..0000000
--- a/app/src/main/res/layout/content_repository.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/coordinator_repository.xml b/app/src/main/res/layout/coordinator_repository.xml
deleted file mode 100644
index c144304..0000000
--- a/app/src/main/res/layout/coordinator_repository.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_display.xml b/app/src/main/res/layout/fragment_display.xml
new file mode 100644
index 0000000..b65cc87
--- /dev/null
+++ b/app/src/main/res/layout/fragment_display.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_program.xml b/app/src/main/res/layout/fragment_program.xml
new file mode 100644
index 0000000..b8d9ce3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_program.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_repository.xml b/app/src/main/res/layout/fragment_repository.xml
new file mode 100644
index 0000000..6fd4262
--- /dev/null
+++ b/app/src/main/res/layout/fragment_repository.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/header_repository.xml b/app/src/main/res/layout/header_repository.xml
deleted file mode 100644
index 998d63c..0000000
--- a/app/src/main/res/layout/header_repository.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_card_se_msp.xml b/app/src/main/res/layout/layout_card_se_msp.xml
index 4b2c6e6..485935a 100644
--- a/app/src/main/res/layout/layout_card_se_msp.xml
+++ b/app/src/main/res/layout/layout_card_se_msp.xml
@@ -1,48 +1,45 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="6dp"
+ android:foreground="?attr/selectableItemBackground"
+ app:cardCornerRadius="4dp"
+ app:cardElevation="2dp">
-
+ android:padding="8dp">
-
-
-
+ android:background="@drawable/shape_background_number"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="1" />
-
+
-
-
-
-
\ No newline at end of file
+
+
+
diff --git a/app/src/main/res/layout/layout_card_se_ssp_mf.xml b/app/src/main/res/layout/layout_card_se_ssp_mf.xml
index b0aea2d..688d99b 100644
--- a/app/src/main/res/layout/layout_card_se_ssp_mf.xml
+++ b/app/src/main/res/layout/layout_card_se_ssp_mf.xml
@@ -1,48 +1,45 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="6dp"
+ android:foreground="?attr/selectableItemBackground"
+ app:cardCornerRadius="4dp"
+ app:cardElevation="2dp">
-
+ android:padding="8dp">
-
-
-
+ android:background="@drawable/shape_background_number"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="1" />
-
+
-
-
-
-
\ No newline at end of file
+
+
+
diff --git a/app/src/main/res/layout/layout_card_se_ssp_sf.xml b/app/src/main/res/layout/layout_card_se_ssp_sf.xml
index 3623cbf..ea09748 100644
--- a/app/src/main/res/layout/layout_card_se_ssp_sf.xml
+++ b/app/src/main/res/layout/layout_card_se_ssp_sf.xml
@@ -1,57 +1,54 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="6dp"
+ android:foreground="?attr/selectableItemBackground"
+ app:cardCornerRadius="4dp"
+ app:cardElevation="2dp">
-
+ android:padding="8dp">
-
+ android:background="@drawable/shape_background_number"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="1" />
-
-
-
+
-
-
-
-
\ No newline at end of file
+
+
+
diff --git a/app/src/main/res/layout/layout_card_single_files_without_sub_parts.xml b/app/src/main/res/layout/layout_card_single_files_without_sub_parts.xml
index 6242aad..6860d9a 100644
--- a/app/src/main/res/layout/layout_card_single_files_without_sub_parts.xml
+++ b/app/src/main/res/layout/layout_card_single_files_without_sub_parts.xml
@@ -1,46 +1,40 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:foreground="?attr/selectableItemBackground">
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:layout_marginStart="16dp"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="@android:color/black"
+ android:textSize="18sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/bullet"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="TextView" />
+
+
+
diff --git a/app/src/main/res/layout/layout_card_single_sub_parts_with_files.xml b/app/src/main/res/layout/layout_card_single_sub_parts_with_files.xml
index 44103ad..e695204 100644
--- a/app/src/main/res/layout/layout_card_single_sub_parts_with_files.xml
+++ b/app/src/main/res/layout/layout_card_single_sub_parts_with_files.xml
@@ -1,50 +1,47 @@
-
-
-
+
+
+ android:layout_height="wrap_content"
+ android:padding="8dp">
-
-
-
+ android:background="@drawable/shape_background_sub_part"
+ android:gravity="center"
+ android:textColor="@color/colorPrimary"
+ android:textSize="18sp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="1" />
-
+
-
+
-
-
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/layout/layout_card_single_sub_parts_without_files.xml b/app/src/main/res/layout/layout_card_single_sub_parts_without_files.xml
index 7f90368..2f747f9 100644
--- a/app/src/main/res/layout/layout_card_single_sub_parts_without_files.xml
+++ b/app/src/main/res/layout/layout_card_single_sub_parts_without_files.xml
@@ -1,45 +1,42 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:foreground="?attr/selectableItemBackground">
-
+ android:padding="8dp">
-
+ android:background="@drawable/shape_background_sub_part"
+ android:gravity="center"
+ android:textColor="@color/colorPrimary"
+ android:textSize="18sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="1" />
-
-
-
+
-
-
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/layout/layout_content_main_activity.xml b/app/src/main/res/layout/layout_content_main_activity.xml
deleted file mode 100644
index b8f8617..0000000
--- a/app/src/main/res/layout/layout_content_main_activity.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_coordinator_main_activity.xml b/app/src/main/res/layout/layout_coordinator_main_activity.xml
deleted file mode 100644
index d08577a..0000000
--- a/app/src/main/res/layout/layout_coordinator_main_activity.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_display_activity.xml b/app/src/main/res/menu/menu_display_fragment.xml
similarity index 83%
rename from app/src/main/res/menu/menu_display_activity.xml
rename to app/src/main/res/menu/menu_display_fragment.xml
index 9d63dbd..3e7a6cf 100644
--- a/app/src/main/res/menu/menu_display_activity.xml
+++ b/app/src/main/res/menu/menu_display_fragment.xml
@@ -1,11 +1,11 @@
\ No newline at end of file
+
diff --git a/app/src/main/res/menu/menu_main_activity.xml b/app/src/main/res/menu/menu_main_fragment.xml
similarity index 69%
rename from app/src/main/res/menu/menu_main_activity.xml
rename to app/src/main/res/menu/menu_main_fragment.xml
index d07c96a..ccaf8ce 100644
--- a/app/src/main/res/menu/menu_main_activity.xml
+++ b/app/src/main/res/menu/menu_main_fragment.xml
@@ -1,5 +1,9 @@
\ No newline at end of file
+
diff --git a/app/src/main/res/menu/menu_program_fragment.xml b/app/src/main/res/menu/menu_program_fragment.xml
new file mode 100644
index 0000000..493ad07
--- /dev/null
+++ b/app/src/main/res/menu/menu_program_fragment.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/main/res/navigation/main_navigation_graph.xml b/app/src/main/res/navigation/main_navigation_graph.xml
new file mode 100644
index 0000000..4b7afe2
--- /dev/null
+++ b/app/src/main/res/navigation/main_navigation_graph.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..de7fde9
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 93f666d..992cea1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,7 @@
VTU CS Lab
+ VTU CS Lab New Version
+ No Internet Connection.\n\nThis content is not available offline yet, Kindly connect to the internet to sync this content locally.
No Internet Connection.
Some Error Occurred.
Copy
@@ -13,4 +15,5 @@
Repository
Exit
Privacy Policy
+ Code copied to Clipboard.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 58b3344..1ca91d6 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -14,6 +14,16 @@
- @color/colorAccent
+
+
diff --git a/app/src/test/java/com/nagpal/shivam/vtucslab/ExampleUnitTest.java b/app/src/test/java/com/nagpal/shivam/vtucslab/ExampleUnitTest.java
index 37d7a91..d40a51e 100644
--- a/app/src/test/java/com/nagpal/shivam/vtucslab/ExampleUnitTest.java
+++ b/app/src/test/java/com/nagpal/shivam/vtucslab/ExampleUnitTest.java
@@ -1,8 +1,8 @@
package com.nagpal.shivam.vtucslab;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.*;
+import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
diff --git a/build.gradle b/build.gradle
index e454feb..297ecb5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,15 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
+ apply from: 'versions.gradle'
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.4.2'
- classpath 'com.google.gms:google-services:4.3.15'
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4'
+ classpath "com.android.tools.build:gradle:$versions.gradle"
+ classpath "com.google.gms:google-services:$versions.google_services"
+ classpath "com.google.firebase:firebase-crashlytics-gradle:$versions.firebase_crashlytics_gradle"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin_gradle_plugin"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$versions.navigation"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
diff --git a/gradle.properties b/gradle.properties
index 40edf30..677429f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,10 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-android.enableJetifier=true
+android.defaults.buildfeatures.buildconfig=true
+android.enableJetifier=false
+android.nonFinalResIds=false
+android.nonTransitiveRClass=false
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1024m
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7076ffe..397aaee 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
diff --git a/versions.gradle b/versions.gradle
new file mode 100644
index 0000000..75d131f
--- /dev/null
+++ b/versions.gradle
@@ -0,0 +1,20 @@
+def versions = [:]
+versions.appcompat = "1.6.1"
+versions.constraintlayout = "2.1.4"
+versions.core_ktx = "1.10.1"
+versions.espresso_core = "3.5.1"
+versions.firebase_bom = "31.2.3"
+versions.firebase_crashlytics_gradle = "2.9.4"
+versions.google_services = "4.3.15"
+versions.gradle = '8.0.2'
+versions.jackson_module_kotlin = "2.14.2"
+versions.junit = "4.13.2"
+versions.kotlin_gradle_plugin = "1.8.0"
+versions.material = "1.9.0"
+versions.multidex = "2.0.1"
+versions.navigation = "2.6.0"
+versions.retrofit = "2.9.0"
+versions.room = "2.5.2"
+versions.swipe_refresh_layout = "1.1.0"
+versions.test_runner = "1.5.2"
+ext.versions = versions