diff --git a/README.md b/README.md index 1dfb2197..0eb2123e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19) [![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)][discord-server] +![Last Commit](https://img.shields.io/github/last-commit/ThmmyNoLife/mTHMMY/develop.svg?style=flat) ![mTHMMY logo](app/src/main/res/mipmap-xhdpi/ic_launcher.png) diff --git a/app/build.gradle b/app/build.gradle index f5dd36bd..a4f3e951 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,15 +6,16 @@ apply plugin: 'com.android.application' apply plugin: 'io.fabric' android { - compileSdkVersion 28 + compileSdkVersion 29 + buildToolsVersion = '29.0.2' defaultConfig { vectorDrawables.useSupportLibrary = true applicationId "gr.thmmy.mthmmy" minSdkVersion 19 - targetSdkVersion 28 - versionCode 16 - versionName "1.6.1" + targetSdkVersion 29 + versionCode 22 + versionName "1.7.4" archivesBaseName = "mTHMMY-v$versionName" buildConfigField "String", "CURRENT_BRANCH", "\"" + getCurrentBranch() + "\"" buildConfigField "String", "COMMIT_HASH", "\"" + getCommitHash() + "\"" @@ -41,44 +42,56 @@ android { } } +def firebaseReleaseProjectId = "mthmmy-release-3aef0" + tasks.whenTaskAdded { task -> if (task.name.contains("assembleRelease")) { task.getDependsOn().add({ - def inputFile = new File("app/google-services.json") - def json = new JsonSlurper().parseText(inputFile.text) - if (json.project_info.project_id != "mthmmy-release-3aef0") - throw new GradleException('Please supply the correct google-services.json for release (or manually change the id above)!') + def googleServicesFile = new File("app/src/release/google-services.json") + if(googleServicesFile.exists()){ + def json = new JsonSlurper().parseText(googleServicesFile.text) + if (json.project_info.project_id != firebaseReleaseProjectId) + throw new GradleException('Please supply the correct google-services.json for release in app/src/release/ directory!') + } + else + throw new GradleException('Please add the release google-services.json in app/src/release/ directory!') }) } else if (task.name.contains("assembleDebug")) { task.getDependsOn().add({ - def inputFile = new File("app/google-services.json") - def json = new JsonSlurper().parseText(inputFile.text) - if (json.project_info.project_id == "mthmmy-release-3aef0") - throw new GradleException('Please replace the release google-services.json with a debug one!') + def googleServicesFile = new File("app/src/debug/google-services.json") + if(googleServicesFile.exists()){ + def json = new JsonSlurper().parseText(googleServicesFile.text) + if (json.project_info.project_id == firebaseReleaseProjectId) + throw new GradleException('Please replace google-services.json in app/src/debug/ with a debug one!') + } + else + throw new GradleException('Please add a debug google-services.json in app/src/debug/ directory!') }) } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.preference:preference:1.1.0-alpha02' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha04' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.exifinterface:exifinterface:1.1.0-beta01' implementation 'com.google.android.material:material:1.0.0' - implementation 'com.google.firebase:firebase-core:16.0.6' - implementation 'com.google.firebase:firebase-messaging:17.3.4' - implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' - implementation 'com.squareup.okhttp3:okhttp:3.12.0' + implementation 'com.google.firebase:firebase-core:17.0.0' + implementation 'com.google.firebase:firebase-messaging:19.0.1' + implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' + implementation 'com.snatik:storage:2.1.0' + implementation 'com.squareup.okhttp3:okhttp:3.12.0' //TODO: Warning: okhttp has dropped support for Android v.19 since okhttp 3.13! implementation 'com.squareup.picasso:picasso:2.5.2' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff! - implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' - implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' + implementation 'com.github.franmontiel:PersistentCookieJar:1.0.1' + implementation 'com.github.PhilJay:MPAndroidChart:3.0.3' implementation 'com.mikepenz:materialdrawer:6.1.1' implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar' @@ -86,11 +99,10 @@ dependencies { implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated! implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'ru.noties:markwon:2.0.0' - implementation 'net.gotev:uploadservice:3.4.2' - implementation 'net.gotev:uploadservice-okhttp:3.4.2' - implementation 'com.itkacher.okhttpprofiler:okhttpprofiler:1.0.4' - //Plugin: https://plugins.jetbrains.com/plugin/11249-okhttp-profiler + implementation 'ru.noties:markwon:2.0.2' + implementation 'net.gotev:uploadservice:3.5.2' + implementation 'net.gotev:uploadservice-okhttp:3.4.2' //TODO: Warning: v.3.5 depends on okhttp 3.13! + implementation 'com.itkacher.okhttpprofiler:okhttpprofiler:1.0.5' //Plugin: https://plugins.jetbrains.com/plugin/11249-okhttp-profiler } apply plugin: 'com.google.gms.google-services' diff --git a/app/gradle/grgit.gradle b/app/gradle/grgit.gradle index dc0d5e7f..da262dd7 100644 --- a/app/gradle/grgit.gradle +++ b/app/gradle/grgit.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'org.ajoberstar.grgit:grgit-core:3.0.0' + classpath 'org.ajoberstar.grgit:grgit-core:3.1.1' } } @@ -48,4 +48,4 @@ ext { getCurrentBranch = this.&getCurrentBranch getCommitHash = this.&getCommitHash isClean = this.&isClean -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e5aa6809..ca0b8150 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + + + + + + + diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java index 77a6aebd..49e9dc8c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java @@ -11,17 +11,21 @@ import android.view.LayoutInflater; import android.view.View; import android.webkit.WebView; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; - -import com.google.android.material.appbar.AppBarLayout; +import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.drawerlayout.widget.DrawerLayout; + +import com.google.android.material.appbar.AppBarLayout; + import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; +import gr.thmmy.mthmmy.base.BaseApplication; public class AboutActivity extends BaseActivity { private static final int TIME_INTERVAL = 1000; @@ -31,8 +35,9 @@ public class AboutActivity extends BaseActivity { private AppBarLayout appBar; private CoordinatorLayout coordinatorLayout; + private ScrollView mainContent; private AlertDialog alertDialog; - private FrameLayout trollGif; + private FrameLayout easterEggImage; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,41 +74,41 @@ public class AboutActivity extends BaseActivity { createDrawer(); drawer.setSelection(ABOUT_ID); - final ScrollView mainContent = findViewById(R.id.scrollview); - trollGif = findViewById(R.id.trollPicFrame); + mainContent = findViewById(R.id.scrollview); + easterEggImage = findViewById(R.id.trollPicFrame); + + // Set Easter egg on logo image + ImageView logoImageView = findViewById(R.id.logoView); + logoImageView.setOnClickListener(view -> { + if (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) { + if (mVersionPressedCounter == TIMES_TO_PRESS) + showEasterEgg(); + mVersionLastPressedTime = System.currentTimeMillis(); + ++mVersionPressedCounter; + } else { + mVersionLastPressedTime = System.currentTimeMillis(); + mVersionPressedCounter = 0; + } + }); - TextView tv = findViewById(R.id.version); - if (tv != null) { + TextView versionTextView = findViewById(R.id.version); + if (versionTextView != null) { if (BuildConfig.DEBUG) - tv.setText(getString(R.string.version, versionName + versionInfo)); + versionTextView.setText(getString(R.string.version, versionName + versionInfo)); else - tv.setText(getString(R.string.version, versionName)); - + versionTextView.setText(getString(R.string.version, versionName)); - if(BuildConfig.DEBUG && gitExists){ - tv.setOnClickListener(view -> { + if(gitExists){ + versionTextView.setOnClickListener(view -> { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/ThmmyNoLife/mTHMMY/commit/" + BuildConfig.COMMIT_HASH)); startActivity(intent); }); } - else{ // Easter Egg - tv.setOnClickListener(view -> { - if (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) { - if (mVersionPressedCounter == TIMES_TO_PRESS) { - appBar.setVisibility(View.INVISIBLE); - mainContent.setVisibility(View.INVISIBLE); - trollGif.setVisibility(View.VISIBLE); - drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - mVersionLastPressedTime = System.currentTimeMillis(); - ++mVersionPressedCounter; - } else { - mVersionLastPressedTime = System.currentTimeMillis(); - mVersionPressedCounter = 0; - } - }); - } + + versionTextView.setOnLongClickListener(view -> { + Toast.makeText(getApplicationContext(), BaseApplication.getFirebaseProjectId(), Toast.LENGTH_SHORT).show(); + return true; + }); } TextView privacyPolicy = findViewById(R.id.privacy_policy_header); @@ -121,6 +126,14 @@ public class AboutActivity extends BaseActivity { super.onResume(); } + @Override + public void onBackPressed() { + if(easterEggImage.getVisibility()==View.INVISIBLE) + super.onBackPressed(); + else + hideEasterEgg(); + } + public void displayApacheLibraries(View v) { LayoutInflater inflater = LayoutInflater.from(this); WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false); @@ -151,4 +164,21 @@ public class AboutActivity extends BaseActivity { alertDialog.getWindow().setLayout(width, height); } + private void showEasterEgg(){ + if(getResources().getConfiguration().orientation==ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){ + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + appBar.setVisibility(View.INVISIBLE); + mainContent.setVisibility(View.INVISIBLE); + easterEggImage.setVisibility(View.VISIBLE); + drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } + } + + private void hideEasterEgg(){ + appBar.setVisibility(View.VISIBLE); + mainContent.setVisibility(View.VISIBLE); + easterEggImage.setVisibility(View.INVISIBLE); + drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java index 29ab9b1b..a24209e5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java @@ -10,10 +10,11 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.Toast; -import com.google.firebase.analytics.FirebaseAnalytics; - import androidx.appcompat.widget.AppCompatButton; import androidx.preference.PreferenceManager; + +import com.google.firebase.analytics.FirebaseAnalytics; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.base.BaseActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java index 41b92813..11d99b1e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java @@ -8,6 +8,11 @@ import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.jsoup.nodes.Document; @@ -17,10 +22,6 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.create_topic.CreateTopicActivity; @@ -100,6 +101,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo progressBar = findViewById(R.id.progressBar); newTopicFAB = findViewById(R.id.board_fab); + newTopicFAB.setTag(true); if (!sessionManager.isLoggedIn()) newTopicFAB.hide(); else { newTopicFAB.setOnClickListener(view -> { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java index 6c5e50db..2e864229 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java @@ -12,10 +12,11 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import java.util.ArrayList; import java.util.Objects; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.model.Board; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java index b42a8caf..3a00881c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java @@ -4,16 +4,17 @@ import android.content.Intent; import android.os.Bundle; import android.widget.Toast; -import com.google.android.material.tabs.TabLayout; - -import java.util.ArrayList; -import java.util.List; - import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import java.util.ArrayList; +import java.util.List; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java index 5b8c8d01..814586ea 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java @@ -12,11 +12,12 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; -import java.util.ArrayList; - import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; + +import java.util.ArrayList; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.model.Bookmark; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java index 4316db4b..e9081fbd 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java @@ -12,11 +12,12 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; -import java.util.ArrayList; - import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; + +import java.util.ArrayList; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.model.Bookmark; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/create_topic/NewTopicTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/create_topic/NewTopicTask.java index 5495d50a..b8ead6b7 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/create_topic/NewTopicTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/create_topic/NewTopicTask.java @@ -61,6 +61,7 @@ public class NewTopicTask extends AsyncTask { .addFormDataPart("sc", sc) .addFormDataPart("subject", strings[1]) .addFormDataPart("topic", topic) + .addFormDataPart("icon", "xx") .build(); Request post = new Request.Builder() diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java index addccf35..95e0334c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java @@ -1,11 +1,21 @@ package gr.thmmy.mthmmy.activities.downloads; +import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -13,10 +23,8 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.upload.UploadActivity; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.model.Download; @@ -29,6 +37,8 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY; + public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { /** * The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. @@ -47,7 +57,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. private MaterialProgressBar progressBar; private RecyclerView recyclerView; private DownloadsAdapter downloadsAdapter; - //private FloatingActionButton uploadFAB; + private FloatingActionButton uploadFAB; private ParseDownloadPageTask parseDownloadPageTask; private int numberOfPages = -1; @@ -113,37 +123,37 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. } }); -// uploadFAB = findViewById(R.id.upload_fab); -// uploadFAB.setEnabled(false); -// uploadFAB.hide(); + uploadFAB = findViewById(R.id.upload_fab); + uploadFAB.setEnabled(false); + uploadFAB.hide(); parseDownloadPageTask = new ParseDownloadPageTask(); parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl); } -// @Override -// public boolean onCreateOptionsMenu(Menu menu) { -// // Inflates the menu; this adds items to the action bar if it is present. -// getMenuInflater().inflate(R.menu.downloads_menu, menu); -// super.onCreateOptionsMenu(menu); -// return true; -// } -// -// @Override -// public boolean onOptionsItemSelected(MenuItem item) { -// // Handle presses on the action bar items -// switch (item.getItemId()) { -// case R.id.menu_upload: -// Intent intent = new Intent(DownloadsActivity.this, UploadActivity.class); -// Bundle extras = new Bundle(); -// extras.putString(BUNDLE_UPLOAD_CATEGORY, downloadsNav); -// intent.putExtras(extras); -// startActivity(intent); -// return true; -// default: -// return super.onOptionsItemSelected(item); -// } -// } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflates the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.downloads_menu, menu); + super.onCreateOptionsMenu(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.menu_upload: + Intent intent = new Intent(DownloadsActivity.this, UploadActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_UPLOAD_CATEGORY, downloadsNav); + intent.putExtras(extras); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } @Override public void onLoadMore() { @@ -198,7 +208,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. @Override protected void onPreExecute() { if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); - //if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false); + if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false); } @Override @@ -296,7 +306,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. toolbar.setTitle(downloadsTitle); ++pagesLoaded; - //if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true); + if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true); progressBar.setVisibility(ProgressBar.INVISIBLE); downloadsAdapter.notifyDataSetChanged(); isLoadingMore = false; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java index 5b6ec26e..d5ca46ad 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java @@ -12,12 +12,13 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Objects; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.model.Download; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java index af1a4cf2..b102c7f7 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java @@ -7,11 +7,6 @@ import android.os.Build; import android.os.Bundle; import android.widget.Toast; -import com.google.android.material.tabs.TabLayout; - -import java.util.ArrayList; -import java.util.List; - import androidx.appcompat.app.AppCompatDelegate; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -19,6 +14,12 @@ import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.preference.PreferenceManager; import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import java.util.ArrayList; +import java.util.List; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java index 718c1b8d..d7ab706a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java @@ -6,14 +6,15 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + import com.bignerdranch.expandablerecyclerview.ChildViewHolder; import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter; import com.bignerdranch.expandablerecyclerview.ParentViewHolder; import java.util.List; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.Board; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java index 70e78af5..f366f295 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java @@ -9,6 +9,10 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.Toast; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter; import org.jsoup.nodes.Document; @@ -19,9 +23,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index 31871069..15589d45 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -6,10 +6,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import java.util.List; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 6c194c0e..6e14b79b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -9,6 +9,10 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.Toast; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import org.jsoup.nodes.Document; import org.jsoup.select.Elements; @@ -17,10 +21,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.session.SessionManager; @@ -145,9 +147,9 @@ public class RecentFragment extends BaseFragment { topicSummaries.addAll(fetchedRecent); recentAdapter.notifyDataSetChanged(); } else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); + Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show(); } else { - Toast.makeText(getContext(), "Unexpected error," + + Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Unexpected error," + " please contact the developers with the details", Toast.LENGTH_LONG).show(); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java index 8810da1a..3818145a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java @@ -5,10 +5,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import java.util.List; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java index 0c47bac5..1db4131e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java @@ -10,6 +10,11 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -18,10 +23,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; @@ -142,11 +143,12 @@ public class UnreadFragment extends BaseFragment { @Override public void onDestroy() { super.onDestroy(); - if (unreadTask.isRunning()) + if (unreadTask!=null && unreadTask.isRunning()) unreadTask.cancel(true); - if (markReadTask.isRunning()) + if (markReadTask!=null && markReadTask.isRunning()) markReadTask.cancel(true); - topicSummaries.clear(); + if(topicSummaries!=null) + topicSummaries.clear(); } public interface UnreadFragmentInteractionListener extends FragmentInteractionListener { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java index b54f9484..5da235b1 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java @@ -19,6 +19,13 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; + import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.tabs.TabLayout; import com.squareup.picasso.Picasso; @@ -33,12 +40,6 @@ import java.util.List; import java.util.Objects; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.core.content.res.ResourcesCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.create_pm.CreatePMActivity; @@ -61,6 +62,7 @@ import timber.log.Timber; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; +import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.emojiTagToHtml; /** * Activity for user profile. When creating an Intent of this activity you need to bundle a String @@ -271,7 +273,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment { //Finds personal text Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); if (tmpEl != null) { - personalText = tmpEl.text().trim(); + personalText = emojiTagToHtml(tmpEl.text().trim()); } else { //Should never get here! //Something is wrong. diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java index 6054a05f..d2a4d1cb 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java @@ -8,9 +8,10 @@ import android.webkit.WebView; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import java.util.ArrayList; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.PostSummary; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java index f2c17ff2..59fa6421 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java @@ -8,6 +8,10 @@ import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.Toast; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -17,9 +21,6 @@ import java.util.ArrayList; import javax.net.ssl.SSLHandshakeException; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseFragment; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java index 8ecf48bd..a0b3dafc 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java @@ -12,6 +12,8 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import androidx.fragment.app.Fragment; + import com.github.mikephil.charting.charts.HorizontalBarChart; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.AxisBase; @@ -37,7 +39,6 @@ import java.util.List; import javax.net.ssl.SSLHandshakeException; -import androidx.fragment.app.Fragment; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java index 12e93a60..4df4598f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java @@ -13,6 +13,8 @@ import android.webkit.WebView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.fragment.app.Fragment; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -21,7 +23,6 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; -import androidx.fragment.app.Fragment; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import timber.log.Timber; @@ -144,7 +145,7 @@ public class SummaryFragment extends Fragment { || summaryRow.text().contains("Κατάσταση")) continue; else if (rowText.contains("Signature") || rowText.contains("Υπογραφή")) { //This needs special handling since it may have css - pHtml = ParseHelpers.youtubeEmbeddedFix(summaryRow); + pHtml = ParseHelpers.emojiTagToHtml(ParseHelpers.youtubeEmbeddedFix(summaryRow)); //Add stuff to make it work in WebView //style.css pHtml = ("\n" + diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java index a884d3a6..c9637dd0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java @@ -3,6 +3,7 @@ package gr.thmmy.mthmmy.activities.settings; import android.os.Bundle; import androidx.fragment.app.FragmentTransaction; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java index a66f91ae..ec5907b6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java @@ -11,12 +11,13 @@ import android.provider.Settings; import android.view.View; import android.widget.Toast; -import java.util.ArrayList; - import androidx.annotation.NonNull; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; + +import java.util.ArrayList; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseApplication; import timber.log.Timber; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java index 82a3389e..9976975e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java @@ -16,6 +16,7 @@ import android.webkit.WebViewClient; import android.widget.TextView; import androidx.annotation.NonNull; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java index 848baa9d..ad42a414 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java @@ -16,7 +16,9 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; + import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.editorview.EditorView; import gr.thmmy.mthmmy.editorview.EmojiKeyboard; import gr.thmmy.mthmmy.model.Shout; @@ -133,6 +135,7 @@ public class ShoutboxFragment extends Fragment { progressBar.setVisibility(View.INVISIBLE); if (resultCode == NetworkResultCodes.SUCCESSFUL) { Timber.i("Shout was sent successfully"); + BaseApplication.getInstance().logFirebaseAnalyticsEvent("shout", null); editorView.getEditText().getText().clear(); shoutboxViewModel.loadShoutbox(true); } else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java index 88c2f8a3..6fb1cc3f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java @@ -2,6 +2,7 @@ package gr.thmmy.mthmmy.activities.topic; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import java.io.IOException; @@ -60,22 +61,24 @@ public class Posting { String finalUrl = response.request().url().toString(); if (finalUrl.contains("action=post")) { Document postErrorPage = Jsoup.parse(response.body().string()); - String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first() - .toString().split("
"); - for (int i = 0; i < errors.length; ++i) { //TODO test - Timber.d(String.valueOf(i)); - Timber.d(errors[i]); - } - for (String error : errors) { - if (error.contains("Your session timed out while posting") || - error.contains("Υπερβήκατε τον μέγιστο χρόνο σύνδεσης κατά την αποστολή")) - return REPLY_STATUS.SESSION_ENDED; - if (error.contains("No subject was filled in") - || error.contains("Δεν δόθηκε τίτλος")) - return REPLY_STATUS.NO_SUBJECT; - if (error.contains("The message body was left empty") - || error.contains("Δεν δόθηκε κείμενο για το μήνυμα")) - return REPLY_STATUS.EMPTY_BODY; + Element errorsElement = postErrorPage.select("tr[id=errors] div[id=error_list]").first(); + if(errorsElement!=null){ + String[] errors = errorsElement.toString().split("
"); + for (int i = 0; i < errors.length; ++i) { //TODO test + Timber.d(String.valueOf(i)); + Timber.d(errors[i]); + } + for (String error : errors) { + if (error.contains("Your session timed out while posting") || + error.contains("Υπερβήκατε τον μέγιστο χρόνο σύνδεσης κατά την αποστολή")) + return REPLY_STATUS.SESSION_ENDED; + if (error.contains("No subject was filled in") + || error.contains("Δεν δόθηκε τίτλος")) + return REPLY_STATUS.NO_SUBJECT; + if (error.contains("The message body was left empty") + || error.contains("Δεν δόθηκε κείμενο για το μήνυμα")) + return REPLY_STATUS.EMPTY_BODY; + } } return REPLY_STATUS.NEW_REPLY_WHILE_POSTING; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java index 64c04437..ff136bd7 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java @@ -28,20 +28,21 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; - -import java.util.ArrayList; - import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatDelegate; import androidx.core.content.res.ResourcesCompat; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask; -import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply; +import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyTask; import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask; import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask; import gr.thmmy.mthmmy.base.BaseActivity; @@ -617,7 +618,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo } } }); - viewModel.setPrepareForReplyCallbacks(new PrepareForReply.PrepareForReplyCallbacks() { + viewModel.setPrepareForReplyCallbacks(new PrepareForReplyTask.PrepareForReplyCallbacks() { @Override public void onPrepareForReplyStarted() { progressBar.setVisibility(ProgressBar.VISIBLE); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index 636d7632..100630de 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java @@ -39,6 +39,14 @@ import android.widget.RadioGroup; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatButton; +import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.RecyclerView; + import com.github.mikephil.charting.charts.HorizontalBarChart; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.YAxis; @@ -53,13 +61,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.AppCompatButton; -import androidx.core.content.res.ResourcesCompat; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; @@ -88,6 +89,7 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.USER_COLOR_WHITE; import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.USER_COLOR_YELLOW; import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager; +import static gr.thmmy.mthmmy.utils.FileUtils.faIconFromFilename; /** * Custom {@link RecyclerView.Adapter} used for topics. @@ -393,7 +395,7 @@ class TopicAdapter extends RecyclerView.Adapter { attached.setClickable(true); attached.setTypeface(Typeface.createFromAsset(context.getAssets() , "fonts/fontawesome-webfont.ttf")); - attached.setText(faIconFromFilename(attachedFile.getFilename()) + " " + attached.setText(faIconFromFilename(context, attachedFile.getFilename()) + " " + attachedFile.getFilename() + attachedFile.getFileInfo()); attached.setTextColor(filesTextColor); attached.setPadding(0, 3, 0, 3); @@ -1036,37 +1038,4 @@ class TopicAdapter extends RecyclerView.Adapter { public interface OnPostFocusChangeListener { void onPostFocusChange(int position); } - - /** - * Returns a String with a single FontAwesome typeface character corresponding to this file's - * extension. - * - * @param filename String with filename containing file's extension - * @return FontAwesome character according to file's type - * @see FontAwesome - */ - @NonNull - private String faIconFromFilename(String filename) { - filename = filename.toLowerCase(); - - if (filename.contains("jpg") || filename.contains("gif") || filename.contains("jpeg") - || filename.contains("png")) - return context.getResources().getString(R.string.fa_file_image_o); - else if (filename.contains("pdf")) - return context.getResources().getString(R.string.fa_file_pdf_o); - else if (filename.contains("zip") || filename.contains("rar") || filename.contains("tar.gz")) - return context.getResources().getString(R.string.fa_file_zip_o); - else if (filename.contains("txt")) - return context.getResources().getString(R.string.fa_file_text_o); - else if (filename.contains("doc") || filename.contains("docx")) - return context.getResources().getString(R.string.fa_file_word_o); - else if (filename.contains("xls") || filename.contains("xlsx")) - return context.getResources().getString(R.string.fa_file_excel_o); - else if (filename.contains("pps")) - return context.getResources().getString(R.string.fa_file_powerpoint_o); - else if (filename.contains("mpg")) - return context.getResources().getString(R.string.fa_file_video_o); - - return context.getResources().getString(R.string.fa_file); - } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java index 3b5b46f0..c54d661c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java @@ -38,6 +38,7 @@ public class EditTask extends AsyncTask { .addFormDataPart("sc", strings[4]) .addFormDataPart("subject", strings[5]) .addFormDataPart("topic", strings[6]) + .addFormDataPart("icon", strings[7]) .build(); Request post = new Request.Builder() .url(strings[0]) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java index a8176072..1a74caa3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java @@ -1,18 +1,19 @@ package gr.thmmy.mthmmy.activities.topic.tasks; public class PrepareForEditResult { - private final String postText, commitEditUrl, numReplies, seqnum, sc, topic; + private final String postText, commitEditUrl, numReplies, seqnum, sc, topic, icon; private int position; private boolean successful; public PrepareForEditResult(String postText, String commitEditUrl, String numReplies, String seqnum, - String sc, String topic, int position, boolean successful) { + String sc, String topic, String icon, int position, boolean successful) { this.postText = postText; this.commitEditUrl = commitEditUrl; this.numReplies = numReplies; this.seqnum = seqnum; this.sc = sc; this.topic = topic; + this.icon = icon; this.position = position; this.successful = successful; } @@ -41,6 +42,10 @@ public class PrepareForEditResult { return topic; } + public String getIcon() { + return icon; + } + public int getPosition() { return position; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java index 6efc2c03..09206a1c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java @@ -38,28 +38,32 @@ public class PrepareForEditTask extends AsyncTaskoption[selected]").first().attr("value"); - return new PrepareForEditResult(postText, commitEditURL, numReplies, seqnum, sc, topic, position, true); + return new PrepareForEditResult(postText, commitEditURL, numReplies, seqnum, sc, topic, icon, position, true); } catch (IOException | Selector.SelectorParseException e) { Timber.e(e, "Prepare failed."); - return new PrepareForEditResult(null, null, null, null, null, null, position, false); + return new PrepareForEditResult(null, null, null, null, null, null, null, position, false); } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyTask.java similarity index 83% rename from app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java rename to app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyTask.java index bf5e12e0..36024a7e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyTask.java @@ -15,13 +15,13 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; -public class PrepareForReply extends AsyncTask { +public class PrepareForReplyTask extends AsyncTask { private PrepareForReplyCallbacks listener; private OnPrepareForReplyFinished finishListener; private String replyPageUrl; - public PrepareForReply(PrepareForReplyCallbacks listener, OnPrepareForReplyFinished finishListener, - String replyPageUrl) { + public PrepareForReplyTask(PrepareForReplyCallbacks listener, OnPrepareForReplyFinished finishListener, + String replyPageUrl) { this.listener = listener; this.finishListener = finishListener; this.replyPageUrl = replyPageUrl; @@ -49,12 +49,16 @@ public class PrepareForReply extends AsyncTask { .addFormDataPart("sc", args[4]) .addFormDataPart("subject", args[0]) .addFormDataPart("topic", args[5]) + .addFormDataPart("icon", "xx") .build(); Request post = new Request.Builder() .url("https://www.thmmy.gr/smf/index.php?action=post2") diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java index 974b8b20..0d9135a4 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java @@ -1,65 +1,86 @@ package gr.thmmy.mthmmy.activities.upload; import android.app.Activity; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; -import android.provider.MediaStore; +import android.text.Editable; +import android.text.Spannable; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.PopupWindow; import android.widget.ProgressBar; +import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceManager; + import com.google.android.material.floatingactionbutton.FloatingActionButton; -import net.gotev.uploadservice.MultipartUploadRequest; -import net.gotev.uploadservice.ServerResponse; -import net.gotev.uploadservice.UploadInfo; +import net.gotev.uploadservice.UploadNotificationAction; import net.gotev.uploadservice.UploadNotificationConfig; -import net.gotev.uploadservice.UploadStatusDelegate; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.File; -import java.io.FileOutputStream; +import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.List; +import java.util.HashMap; import java.util.Locale; +import java.util.UUID; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.upload.multipart.MultipartUploadRequest; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.model.UploadCategory; +import gr.thmmy.mthmmy.model.UploadFile; +import gr.thmmy.mthmmy.services.UploadsReceiver; import gr.thmmy.mthmmy.utils.AppCompatSpinnerWithoutDefault; +import gr.thmmy.mthmmy.utils.FileUtils; +import gr.thmmy.mthmmy.utils.TakePhoto; import gr.thmmy.mthmmy.utils.parsing.ParseException; import gr.thmmy.mthmmy.utils.parsing.ParseTask; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import timber.log.Timber; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.UPLOADING_APP_SIGNATURE_ENABLE_KEY; -import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_COURSE; +import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_GREEKLISH_NAME; +import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_MINIFIED_NAME; +import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_NAME; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_DESCRIPTION; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_FILENAME; import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_TITLE; +import static gr.thmmy.mthmmy.utils.FileUtils.faIconFromFilename; public class UploadActivity extends BaseActivity { /** @@ -67,31 +88,50 @@ public class UploadActivity extends BaseActivity { */ public static final String BUNDLE_UPLOAD_CATEGORY = "UPLOAD_CATEGORY"; private static final String uploadIndexUrl = "https://www.thmmy.gr/smf/index.php?action=tpmod;dl=upload"; - private static final String uploadedFromThmmyPromptHtml = "
uploaded from mTHMMY"; + private static final String uploadedFromTHMMYPromptHtml = "
uploaded from mTHMMY"; /** * Request codes used in activities for result (AFR) calls */ - private static final int AFR_REQUEST_CODE_CHOOSE_FILE = 8; - private static final int AFR_REQUEST_CODE_CAMERA = 4; - private static final int AFR_REQUEST_CODE_FIELDS_BUILDER = 74; + private static final int AFR_REQUEST_CODE_CHOOSE_FILE = 8; //Arbitrary, application specific + private static final int AFR_REQUEST_CODE_CAMERA = 4; //Arbitrary, application specific + private static final int AFR_REQUEST_CODE_FIELDS_BUILDER = 74; //Arbitrary, application specific + + /** + * Request codes to gain camera and read/write permission + */ + private static final int UPLOAD_REQUEST_CAMERA_CODE = 42; //Arbitrary, application specific + private static final int UPLOAD_REQUEST_STORAGE_CODE = 12; //Arbitrary, application specific + + private static final int MAX_FILE_SIZE_SUPPORTED = 45000000; + + private HashMap uploadsCourses; private ArrayList uploadRootCategories = new ArrayList<>(); private ParseUploadPageTask parseUploadPageTask; private ArrayList bundleCategory; private String categorySelected = "-1"; private String uploaderProfileIndex = "1"; - private String uploadFilename; - private Uri fileUri; + private UploadsCourse uploadsCourse; + private String semester = ""; + + private ArrayList filesList = new ArrayList<>(); + private File photoFileCreated = null; private String fileIcon; + private AppCompatImageButton uploadFilenameInfo; + private CustomTextWatcher textWatcher; + private boolean hasModifiedFilename = false; + + private ZipTask zipTask; //UI elements private MaterialProgressBar progressBar; private LinearLayout categoriesSpinners; private AppCompatSpinnerWithoutDefault rootCategorySpinner; private EditText uploadTitle; + private EditText uploadFilename; private EditText uploadDescription; - private AppCompatButton titleDescriptionBuilderButton; - private AppCompatTextView filenameHolder; + private AppCompatButton generateFieldsButton; + private LinearLayout filesListView; @Override protected void onCreate(Bundle savedInstanceState) { @@ -134,72 +174,54 @@ public class UploadActivity extends BaseActivity { rootCategorySpinner = findViewById(R.id.upload_spinner_category_root); rootCategorySpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(uploadRootCategories)); - titleDescriptionBuilderButton = findViewById(R.id.upload_title_description_builder); - titleDescriptionBuilderButton.setOnClickListener(view -> { - if (categorySelected.equals("-1")) { - Toast.makeText(view.getContext(), "Please choose a category first", Toast.LENGTH_SHORT).show(); - return; + generateFieldsButton = findViewById(R.id.upload_title_description_builder); + generateFieldsButton.setEnabled(false); + generateFieldsButton.setOnClickListener(view -> { + if(uploadsCourse!=null && !uploadsCourse.getName().equals("") && !semester.equals("")){ + Intent intent = new Intent(UploadActivity.this, UploadFieldsBuilderActivity.class); + Bundle builderExtras = new Bundle(); + builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_NAME, uploadsCourse.getName()); + builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_MINIFIED_NAME, uploadsCourse.getMinifiedName()); + builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_GREEKLISH_NAME, uploadsCourse.getGreeklishName()); + builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER, semester); + intent.putExtras(builderExtras); + startActivityForResult(intent, AFR_REQUEST_CODE_FIELDS_BUILDER); } + }); - int numberOfSpinners = categoriesSpinners.getChildCount(); + uploadTitle = findViewById(R.id.upload_title); + uploadDescription = findViewById(R.id.upload_description); - if (numberOfSpinners < 3) { - Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show(); + uploadFilenameInfo = findViewById(R.id.upload_filename_info); + uploadFilenameInfo.setOnClickListener(view -> { + //Inflates the popup menu content + LayoutInflater layoutInflater = (LayoutInflater) view.getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + if (layoutInflater == null) { return; } - String maybeSemester = "", maybeCourse = ""; - - if (numberOfSpinners == 5) { - if (((AppCompatSpinnerWithoutDefault) categoriesSpinners.getChildAt(numberOfSpinners - 1)). - getSelectedItemPosition() == -1) { - maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 4)).getSelectedItem(); - maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem(); - } else { - Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show(); - } - } else if (numberOfSpinners == 4) { - maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 3)).getSelectedItem(); - maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem(); - } else { - maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem(); - maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) - categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem(); - } - - if (!maybeSemester.contains("εξάμηνο") && !maybeSemester.contains("Εξάμηνο")) { - Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show(); - return; - } - if (maybeCourse == null) { - Toast.makeText(view.getContext(), "Please choose a course", Toast.LENGTH_SHORT).show(); - return; - } + Context wrapper = new ContextThemeWrapper(this, R.style.PopupWindow); + View popUpContent = layoutInflater.inflate(R.layout.activity_upload_filename_info_popup, null); - //Fixes course and semester - String course = maybeCourse.replaceAll("-", "").replace("(ΝΠΣ)", "").trim(); - String semester = maybeSemester.replaceAll("-", "").trim().substring(0, 1); + //Creates the PopupWindow + PopupWindow popUp = new PopupWindow(wrapper); + popUp.setContentView(popUpContent); + popUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT); + popUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT); + popUp.setFocusable(true); - Intent intent = new Intent(UploadActivity.this, UploadFieldsBuilderActivity.class); - Bundle builderExtras = new Bundle(); - builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE, course); - builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER, semester); - intent.putExtras(builderExtras); - startActivityForResult(intent, AFR_REQUEST_CODE_FIELDS_BUILDER); + ((TextView) popUpContent.findViewById(R.id.upload_filename_info_text)). + setMovementMethod(LinkMovementMethod.getInstance()); + //Displays the popup + popUp.showAsDropDown(view); }); - titleDescriptionBuilderButton.setEnabled(false); - uploadTitle = findViewById(R.id.upload_title); - uploadDescription = findViewById(R.id.upload_description); + uploadFilename = findViewById(R.id.upload_filename); + textWatcher = new CustomTextWatcher(); + uploadFilename.addTextChangedListener(textWatcher); - filenameHolder = findViewById(R.id.upload_filename); - Drawable filenameDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_attach_file_white_24dp); - filenameHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(filenameDrawable, null, null, null); + filesListView = findViewById(R.id.upload_files_list); AppCompatButton selectFileButton = findViewById(R.id.upload_select_file_button); Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp); @@ -210,9 +232,10 @@ public class UploadActivity extends BaseActivity { "application/msword", "image/vnd.djvu", "application/gz", "application/tar.gz"}; Intent intent = new Intent(Intent.ACTION_GET_CONTENT) - //.setType("*/*") - .setType("image/jpeg") - .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + .setType("*/*") + //.setType("image/jpeg") + .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); startActivityForResult(intent, AFR_REQUEST_CODE_CHOOSE_FILE); }); @@ -221,123 +244,177 @@ public class UploadActivity extends BaseActivity { Drawable takePhotoDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_photo_camera_white_24dp); takePhotoButton.setCompoundDrawablesRelativeWithIntrinsicBounds(takePhotoDrawable, null, null, null); takePhotoButton.setOnClickListener(v -> { - Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - takePhotoIntent.putExtra("return-data", true); - takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(UploadsHelper.getCacheFile(this))); - - Intent targetedIntent = new Intent(takePhotoIntent); - List resInfo = this.getPackageManager().queryIntentActivities(takePhotoIntent, 0); - for (ResolveInfo resolveInfo : resInfo) { - String packageName = resolveInfo.activityInfo.packageName; - targetedIntent.setPackage(packageName); - } - startActivityForResult(takePhotoIntent, AFR_REQUEST_CODE_CAMERA); + if (checkPerms()) + takePhoto(); + else + requestPerms(UPLOAD_REQUEST_CAMERA_CODE); }); FloatingActionButton uploadFAB = findViewById(R.id.upload_fab); + uploadFAB.setTag(true); uploadFAB.setOnClickListener(view -> { + //Attempts upload progressBar.setVisibility(View.VISIBLE); String uploadTitleText = uploadTitle.getText().toString(); - String uploadDescriptionText = uploadDescription.getText().toString(); - - if (uploadTitleText.equals("")) { - uploadTitle.setError("Required"); - } - if (fileUri == null) { - Toast.makeText(view.getContext(), "Please choose a file to upload or take a photo", Toast.LENGTH_LONG).show(); - } - if (categorySelected.equals("-1")) { - Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show(); - } - - if (categorySelected.equals("-1") || uploadTitleText.equals("") || fileUri == null) { - progressBar.setVisibility(View.GONE); - return; - } - - SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); - if (sharedPrefs.getBoolean(UPLOADING_APP_SIGNATURE_ENABLE_KEY, true)) { - uploadDescriptionText += uploadedFromThmmyPromptHtml; - } + String editTextFilename = uploadFilename.getText().toString(); + final String[] uploadDescriptionText = {uploadDescription.getText().toString()}; + + //Checks if all required fields are filled + { + boolean shouldReturn = false; + if (uploadTitleText.equals("")) { + uploadTitle.setError("Required"); + shouldReturn = true; + } + if (filesList.isEmpty()) { + Toast.makeText(view.getContext(), "Please choose a file to upload or take a photo", Toast.LENGTH_LONG).show(); + shouldReturn = true; + } + if (categorySelected.equals("-1")) { + Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show(); + shouldReturn = true; + } + if (!filesList.isEmpty()) { + long totalFilesSize = 0; + for (UploadFile file : filesList) { + totalFilesSize += FileUtils.sizeFromUri(this, file.getFileUri()); + } - String tempFilePath = null; - if (uploadFilename != null) { - //File should be uploaded with a certain name. Temporarily copies the file and renames it - tempFilePath = UploadsHelper.createTempFile(this, fileUri, uploadFilename); - if (tempFilePath == null) { - //Something went wrong, abort - Toast.makeText(this, "Could not create temporary file for renaming", Toast.LENGTH_SHORT).show(); + if (totalFilesSize > MAX_FILE_SIZE_SUPPORTED) { + Toast.makeText(view.getContext(), "Your files are too powerful for thmmy. Reduce size or split!", Toast.LENGTH_LONG).show(); + shouldReturn = true; + } + } + if (!editTextFilename.matches("(.+\\.)+.+") || + !FileUtils.getFilenameWithoutExtension(editTextFilename). + matches("[0-9a-zA-Zα-ωΑ-Ω~!@#$%^&()_+=\\-`\\[\\]{};',.]+")) { + uploadFilename.setError("Invalid filename"); + shouldReturn = true; + } + if (shouldReturn) { progressBar.setVisibility(View.GONE); return; } } - try { - new MultipartUploadRequest(view.getContext(), uploadIndexUrl) - .setUtf8Charset() - .addParameter("tp-dluploadtitle", uploadTitleText) - .addParameter("tp-dluploadcat", categorySelected) - .addParameter("tp-dluploadtext", uploadDescriptionText) - .addFileToUpload(tempFilePath == null - ? fileUri.toString() - : tempFilePath - , "tp-dluploadfile") - .addParameter("tp_dluploadicon", fileIcon) - .addParameter("tp-uploaduser", uploaderProfileIndex) - .setNotificationConfig(new UploadNotificationConfig()) - .setMaxRetries(2) - .setDelegate(new UploadStatusDelegate() { - @Override - public void onProgress(Context context, UploadInfo uploadInfo) { - } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Upload to thmmy"); + builder.setMessage("Are you sure?"); + builder.setPositiveButton("YES, FIRE AWAY", (dialog, which) -> { + //Checks settings and possibly adds "Uploaded from mTHMMY" string to description + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); + if (sharedPrefs.getBoolean(UPLOADING_APP_SIGNATURE_ENABLE_KEY, true)) { + uploadDescriptionText[0] += uploadedFromTHMMYPromptHtml; + } - @Override - public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, - Exception exception) { - Toast.makeText(context, "Upload failed", Toast.LENGTH_SHORT).show(); - UploadsHelper.deleteTempFiles(); - progressBar.setVisibility(View.GONE); - } + for (UploadFile file : filesList) { + if (file.isCameraPhoto()) { + TakePhoto.galleryAddPic(this, file.getPhotoFile()); + } + } - @Override - public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { - Toast.makeText(context, "Upload completed successfully", Toast.LENGTH_SHORT).show(); - UploadsHelper.deleteTempFiles(); - BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_upload", null); - - uploadTitle.setText(null); - uploadDescription.setText(null); - fileUri = null; - filenameHolder.setText(null); - filenameHolder.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); + Uri tempFileUri = null; + if (filesList.size() == 1) { + //Checks if the file needs renaming + UploadFile uploadFile = filesList.get(0); + String selectedFileFilename = FileUtils.filenameFromUri(this, uploadFile.getFileUri()); + + if (!editTextFilename.equals(selectedFileFilename)) { + //File should be uploaded with a different name + + if (checkPerms()) { + if (!uploadFile.isCameraPhoto()) { + //Temporarily copies the file to a another location and renames it + tempFileUri = UploadsHelper.createTempFile(this, storage, + uploadFile.getFileUri(), + FileUtils.getFilenameWithoutExtension(editTextFilename)); + } else { + //Renames the photo taken + String photoPath = uploadFile.getPhotoFile().getPath(); + photoPath = photoPath.substring(0, photoPath.lastIndexOf(File.separator)); + String destinationFilename = photoPath + File.separator + + FileUtils.getFilenameWithoutExtension(editTextFilename) + ".jpg"; + + if (!storage.rename(uploadFile.getPhotoFile().getAbsolutePath(), destinationFilename)) { + //Something went wrong, abort + Toast.makeText(this, "Could not create temporary file for renaming", Toast.LENGTH_SHORT).show(); + progressBar.setVisibility(View.GONE); + return; + } + + //Points photoFile and fileUri to the new copied and renamed file + uploadFile.setPhotoFile(storage.getFile(destinationFilename)); + uploadFile.setFileUri(FileProvider.getUriForFile(this, getPackageName() + + ".provider", uploadFile.getPhotoFile())); } + } else { + requestPerms(UPLOAD_REQUEST_STORAGE_CODE); + zipTask = null; + dialog.cancel(); + return; + } + } + } else { + Uri[] filesListArray = new Uri[filesList.size()]; + for (int i = 0; i < filesList.size(); ++i) { + filesListArray[i] = filesList.get(i).getFileUri(); + } - @Override - public void onCancelled(Context context, UploadInfo uploadInfo) { - Toast.makeText(context, "Upload canceled", Toast.LENGTH_SHORT).show(); + zipTask = new ZipTask(this, editTextFilename, categorySelected, + uploadTitleText, uploadDescriptionText[0], fileIcon, + uploaderProfileIndex); - UploadsHelper.deleteTempFiles(); - progressBar.setVisibility(View.GONE); - } - }) - .startUpload(); - } catch (Exception exception) { - Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); + if (checkPerms()) { + zipTask.execute(filesListArray); + finish(); + } else { + requestPerms(UPLOAD_REQUEST_STORAGE_CODE); + dialog.cancel(); + } + return; + } + + String uploadID = UUID.randomUUID().toString(); + if (uploadFile(this, uploadID, getConfigForUpload(this, uploadID, + editTextFilename), + categorySelected, uploadTitleText, + uploadDescriptionText[0], fileIcon, uploaderProfileIndex, + tempFileUri == null + ? filesList.get(0).getFileUri() + : tempFileUri)) { + finish(); + } else { + Toast.makeText(this, "Couldn't initiate upload.", Toast.LENGTH_SHORT).show(); + } + }); + + builder.setNegativeButton("NOPE", (dialog, which) -> { progressBar.setVisibility(View.GONE); - } + dialog.dismiss(); + }); + + AlertDialog alert = builder.create(); + alert.setOnCancelListener(dialog -> { + progressBar.setVisibility(View.GONE); + dialog.dismiss(); + }); + alert.show(); }); if (uploadRootCategories.isEmpty()) { //Parses the uploads page parseUploadPageTask = new ParseUploadPageTask(); - parseUploadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uploadIndexUrl); + parseUploadPageTask.execute(uploadIndexUrl); } else { //Renders the already parsed data updateUIElements(); - titleDescriptionBuilderButton.setEnabled(true); + generateFieldsButton.setEnabled(true); } + + Resources res = getResources(); + uploadsCourses = new HashMap<>(UploadsCourse + .generateUploadsCourses(res.getStringArray(R.array.string_array_uploads_courses))); } @Override @@ -355,11 +432,17 @@ public class UploadActivity extends BaseActivity { super.onResume(); } + @Override + protected void onPause() { + super.onPause(); + } + @Override protected void onDestroy() { super.onDestroy(); - if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING) + if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING) { parseUploadPageTask.cancel(true); + } } @Override @@ -369,71 +452,108 @@ public class UploadActivity extends BaseActivity { return; } - fileUri = data.getData(); - if (fileUri != null) { - String filename = UploadsHelper.filenameFromUri(this, fileUri); - filenameHolder.setText(filename); - filenameHolder.setVisibility(View.VISIBLE); - - filename = filename.toLowerCase(); - if (filename.endsWith(".jpg")) { - fileIcon = "jpg_image.gif"; - } else if (filename.endsWith(".gif")) { - fileIcon = "gif_image.gif"; - } else if (filename.endsWith(".png")) { - fileIcon = "png_image.gif"; - } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { - fileIcon = "html_file.gif"; - } else if (filename.endsWith(".pdf") || filename.endsWith(".doc") || - filename.endsWith("djvu")) { - fileIcon = "text_file.gif"; - } else if (filename.endsWith(".zip") || filename.endsWith(".rar") || - filename.endsWith(".tar") || filename.endsWith(".tar.gz") || - filename.endsWith(".gz")) { - fileIcon = "archive.gif"; - } else { - fileIcon = "blank.gif"; + if (data.getClipData() != null) { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); + + if (!hasModifiedFilename) + setZipUploadFilename(); + + for (int fileIndex = 0; fileIndex < data.getClipData().getItemCount(); ++fileIndex) { + Uri newFileUri = data.getClipData().getItemAt(fileIndex).getUri(); + String filename = FileUtils.filenameFromUri(this, newFileUri); + addFileViewToList(filename); + filesList.add(new UploadFile(false, newFileUri, null)); + } + } else { + Uri newFileUri = data.getData(); + if (newFileUri != null) { + String filename = FileUtils.filenameFromUri(this, newFileUri); + + if (filesList.isEmpty()) { + textWatcher.setFileExtension(FileUtils.getFileExtension(filename)); + + if (!hasModifiedFilename) { + uploadFilename.setText(filename); + hasModifiedFilename = false; + } + + filename = filename.toLowerCase(); + if (filename.endsWith(".jpg")) { + fileIcon = "jpg_image.gif"; + } else if (filename.endsWith(".gif")) { + fileIcon = "gif_image.gif"; + } else if (filename.endsWith(".png")) { + fileIcon = "png_image.gif"; + } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { + fileIcon = "html_file.gif"; + } else if (filename.endsWith(".pdf") || filename.endsWith(".doc") || + filename.endsWith("djvu")) { + fileIcon = "text_file.gif"; + } else if (filename.endsWith(".zip") || filename.endsWith(".rar") || + filename.endsWith(".tar") || filename.endsWith(".tar.gz") || + filename.endsWith(".gz")) { + fileIcon = "archive.gif"; + } else { + fileIcon = "blank.gif"; + } + } else { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); + + if (!hasModifiedFilename) + setZipUploadFilename(); + } + + addFileViewToList(filename); + filesList.add(new UploadFile(false, newFileUri, null)); } } } else if (requestCode == AFR_REQUEST_CODE_CAMERA) { if (resultCode == Activity.RESULT_CANCELED) { + //Deletes image file + storage.deleteFile(photoFileCreated.getAbsolutePath()); return; } - Bitmap bitmap; - File cacheImageFile = UploadsHelper.getCacheFile(this); + if (filesList.isEmpty()) { + textWatcher.setFileExtension(FileUtils.getFileExtension(photoFileCreated.getName())); - Uri cacheFileUri = Uri.fromFile(cacheImageFile); - fileIcon = "jpg_image.gif"; + if (!hasModifiedFilename) { + uploadFilename.setText(photoFileCreated.getName()); + hasModifiedFilename = false; + } - bitmap = UploadsHelper.getImageResized(this, cacheFileUri); - int rotation = UploadsHelper.getRotation(this, cacheFileUri); - bitmap = UploadsHelper.rotate(bitmap, rotation); + fileIcon = "jpg_image.gif"; + } else { + fileIcon = "archive.gif"; + textWatcher.setFileExtension(".zip"); - try { - FileOutputStream out = new FileOutputStream(cacheImageFile); - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); - out.flush(); - out.close(); - } catch (Exception e) { - e.printStackTrace(); + if (!hasModifiedFilename) + setZipUploadFilename(); } - String newFilename = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE). - format(new Date()); - fileUri = Uri.parse(UploadsHelper.createTempFile(this, cacheFileUri, newFilename)); - - newFilename += ".jpg"; - filenameHolder.setText(newFilename); - filenameHolder.setVisibility(View.VISIBLE); - - UploadsHelper.deleteCacheFiles(this); + UploadFile newFile = new UploadFile(true, TakePhoto.processResult(this, + photoFileCreated), photoFileCreated); + addFileViewToList(FileUtils.getFilenameWithoutExtension(FileUtils. + filenameFromUri(this, newFile.getFileUri()))); + filesList.add(newFile); } else if (requestCode == AFR_REQUEST_CODE_FIELDS_BUILDER) { if (resultCode == Activity.RESULT_CANCELED) { return; } - uploadFilename = data.getStringExtra(RESULT_FILENAME); + String previousName = uploadFilename.getText().toString(); + if (previousName.isEmpty()) { + uploadFilename.setText(data.getStringExtra(RESULT_FILENAME)); + } else { + String extractedExtension = FileUtils.getFileExtension(previousName); + String filenameWithExtension = data.getStringExtra(RESULT_FILENAME) + + (extractedExtension != null ? extractedExtension : ""); + uploadFilename.setText(filenameWithExtension); + } + hasModifiedFilename = true; + uploadTitle.setText(data.getStringExtra(RESULT_TITLE)); uploadDescription.setText(data.getStringExtra(RESULT_DESCRIPTION)); } else { @@ -441,6 +561,265 @@ public class UploadActivity extends BaseActivity { } } + @Override + public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions + , @NonNull int[] grantResults) { + switch (permsRequestCode) { + case UPLOAD_REQUEST_CAMERA_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + takePhoto(); + break; + case UPLOAD_REQUEST_STORAGE_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && + zipTask != null) { + Uri[] filesListArray = new Uri[filesList.size()]; + for (int i = 0; i < filesList.size(); ++i) { + filesListArray[i] = filesList.get(i).getFileUri(); + } + + zipTask.execute(filesListArray); + finish(); + } else { + Toast.makeText(this, "Please retry uploading.", Toast.LENGTH_SHORT).show(); + } + break; + } + } + + private void setZipUploadFilename(){ + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String zipFilename = "mTHMMY_" + timeStamp + ".zip"; + uploadFilename.setText(zipFilename); + hasModifiedFilename = false; + } + + // Should only be called after making sure permissions are granted + private void takePhoto() { + // Create the File where the photo should go + photoFileCreated = TakePhoto.createImageFile(this); + + // Continue only if the File was successfully created + if (photoFileCreated != null) { + startActivityForResult(TakePhoto.getIntent(this, photoFileCreated), + AFR_REQUEST_CODE_CAMERA); + } + } + + private void updateUIElements() { + String[] tmpSpinnerArray = new String[uploadRootCategories.size()]; + for (int i = 0; i < uploadRootCategories.size(); ++i) { + tmpSpinnerArray[i] = uploadRootCategories.get(i).getCategoryTitle(); + } + + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(BaseApplication.getInstance().getApplicationContext(), + R.layout.spinner_item, tmpSpinnerArray); + spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item); + rootCategorySpinner.setAdapter(spinnerArrayAdapter); + + //Sets bundle selection + if (bundleCategory != null) { + int bundleSelectionIndex = -1, currentIndex = 0; + + for (UploadCategory category : uploadRootCategories) { + if (bundleCategory.get(0).contains(category.getCategoryTitle())) { + bundleSelectionIndex = currentIndex; + break; + } + ++currentIndex; + } + + if (bundleSelectionIndex != -1) { + rootCategorySpinner.setSelection(bundleSelectionIndex, true); + bundleCategory.remove(0); + } + } + } + + private void addFileViewToList(String filename) { + LayoutInflater layoutInflater = getLayoutInflater(); + LinearLayout newFileRow = (LinearLayout) layoutInflater. + inflate(R.layout.activity_upload_file_list_row, null); + + TextView itemText = newFileRow.findViewById(R.id.upload_file_item_text); + itemText.setTypeface(Typeface.createFromAsset(this.getAssets() + , "fonts/fontawesome-webfont.ttf")); + String filenameWithIcon = faIconFromFilename(this, filename) + " " + filename; + itemText.setText(filenameWithIcon); + + newFileRow.findViewById(R.id.upload_file_item_remove).setOnClickListener(view -> { + int fileIndex = filesListView.indexOfChild(newFileRow); + filesListView.removeViewAt(fileIndex); + + if (filesList.get(fileIndex).isCameraPhoto()) { + storage.deleteFile(filesList.get(fileIndex).getPhotoFile().getAbsolutePath()); + } + filesList.remove(fileIndex); + if (filesList.isEmpty()) { + filesListView.setVisibility(View.GONE); + } else if (filesList.size() == 1) { + textWatcher.setFileExtension(FileUtils.getFileExtension(FileUtils. + filenameFromUri(this, filesList.get(0).getFileUri()))); + } + }); + + filesListView.addView(newFileRow); + filesListView.setVisibility(View.VISIBLE); + } + + public UploadNotificationConfig getConfigForUpload(Context context, String uploadID, + String filename) { + UploadNotificationConfig uploadNotificationConfig = new UploadNotificationConfig(); + uploadNotificationConfig.setIconForAllStatuses(android.R.drawable.stat_sys_upload); + uploadNotificationConfig.setTitleForAllStatuses("Uploading " + filename); + + uploadNotificationConfig.getProgress().iconResourceID = android.R.drawable.stat_sys_upload; + uploadNotificationConfig.getCompleted().iconResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getError().iconResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getError().iconColorResourceID = R.color.error_red; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadNotificationConfig.getError().message = "Error during upload. Click for options"; + } + uploadNotificationConfig.getCancelled().iconColorResourceID = android.R.drawable.stat_sys_upload_done; + uploadNotificationConfig.getCancelled().autoClear = true; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + Intent combinedActionsIntent = new Intent(UploadsReceiver.ACTION_COMBINED_UPLOAD); + combinedActionsIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + /*combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_FILENAME, filename); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_CATEGORY, retryCategory); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_TITLE, retryTitleText); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_DESCRIPTION, retryDescription); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_ICON, retryIcon); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_UPLOADER, retryUploaderProfile); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_RETRY_FILE_URI, retryFileUri);*/ + + uploadNotificationConfig.setClickIntentForAllStatuses(PendingIntent.getBroadcast(context, + 1, combinedActionsIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } + else { + Intent retryIntent = new Intent(context, UploadsReceiver.class); + retryIntent.setAction(UploadsReceiver.ACTION_RETRY_UPLOAD); + retryIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + Intent cancelIntent = new Intent(context, UploadsReceiver.class); + cancelIntent.setAction(UploadsReceiver.ACTION_CANCEL_UPLOAD); + cancelIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadID); + + uploadNotificationConfig.getProgress().actions.add(new UploadNotificationAction( + R.drawable.ic_cancel_accent_24dp, + context.getString(R.string.cancel), + PendingIntent.getBroadcast(context, 0, cancelIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + )); + uploadNotificationConfig.getError().actions.add(new UploadNotificationAction( + R.drawable.ic_notification, + context.getString(R.string.upload_retry), + PendingIntent.getBroadcast(context, 0, retryIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + )); + } + + return uploadNotificationConfig; + } + + public static boolean uploadFile(Context context, String uploadID, + UploadNotificationConfig uploadNotificationConfig, + String categorySelected, String uploadTitleText, + String uploadDescriptionText, String fileIcon, + String uploaderProfileIndex, Uri fileUri) { + try { + MultipartUploadRequest multipartUploadRequest = new MultipartUploadRequest(context, uploadID, uploadIndexUrl) + .setUtf8Charset() + .setNotificationConfig(uploadNotificationConfig) + .addParameter("tp-dluploadtitle", uploadTitleText) + .addParameter("tp-dluploadcat", categorySelected) + .addParameter("tp-dluploadtext", uploadDescriptionText) + .addFileToUpload(fileUri.toString() + , "tp-dluploadfile") + .addParameter("tp_dluploadicon", fileIcon) + .addParameter("tp_dluploadpic", "") + .addParameter("tp-uploaduser", uploaderProfileIndex) + .setNotificationConfig(uploadNotificationConfig) + .setMaxRetries(2); + Timber.d("Uploading a file with properties: \nTitle: %s\nCategory: %s\nDescription: %s\nIcon: %s\nUploader: %s", + uploadTitleText, categorySelected, uploadDescriptionText, fileIcon, uploaderProfileIndex); + multipartUploadRequest.startUpload(); + Toast.makeText(context, "Uploading file(s) in the background...", Toast.LENGTH_SHORT).show(); + return true; + } catch (Exception exception) { + Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); + return false; + } + } + + private class CustomTextWatcher implements TextWatcher { + String oldFilename, fileExtension; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + //Saves an instance of the filename before changing + oldFilename = s.toString(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { } + + @Override + public void afterTextChanged(Editable s) { + //Warns user for bad filenames + String filenameWithoutExtension = FileUtils.getFilenameWithoutExtension(s.toString()); + if (filenameWithoutExtension != null && !filenameWithoutExtension.isEmpty() && + !filenameWithoutExtension.matches("[0-9a-zA-Z~!@#$%^&()_+=\\-`\\[\\]{};',.]+")) { + uploadFilenameInfo.setImageResource(R.drawable.ic_info_outline_warning_24dp); + } else { + uploadFilenameInfo.setImageResource(R.drawable.ic_info_outline_white_24dp); + } + + if (fileExtension == null) { + hasModifiedFilename = !s.toString().isEmpty(); + return; + } + + if (!s.toString().endsWith(fileExtension)) { + //User tried to alter the extension + //Prevents the change + uploadFilename.setText(oldFilename); + return; + } + + //User has modified the filename + hasModifiedFilename = true; + if (s.toString().isEmpty() || (filesList.size() == 1 && s.toString().equals(FileUtils. + filenameFromUri(getApplicationContext(), filesList.get(0).getFileUri())))) { + //After modification the filename falls back to the original + hasModifiedFilename = false; + } + + //Adds the grey colored span to the extension + s.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.secondary_text)), + s.length() - fileExtension.length(), s.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + void setFileExtension(String extension) { + boolean oldHasModifiedFilename = hasModifiedFilename; + oldFilename = uploadFilename.getText().toString(); + fileExtension = extension; + String newFilename; + + if (!oldFilename.isEmpty()) { + newFilename = FileUtils.getFilenameWithoutExtension(oldFilename) + extension; + } else { + newFilename = extension; + } + + uploadFilename.setText(newFilename); + hasModifiedFilename = oldHasModifiedFilename; + } + } + private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener { private ArrayList parentCategories, childCategories; @@ -463,6 +842,7 @@ public class UploadActivity extends BaseActivity { } categorySelected = parentCategories.get(position).getValue(); + generateFieldsButton.setEnabled(false); //Adds new sub-category spinner if (parentCategories.get(position).hasSubCategories()) { @@ -503,10 +883,63 @@ public class UploadActivity extends BaseActivity { } } } + else + setCourseAndSemester(); } @Override - public void onNothingSelected(AdapterView parent) { + public void onNothingSelected(AdapterView parent) { } + + private void setCourseAndSemester(){ + uploadsCourse = null; + semester = ""; + if (categorySelected.equals("-1")) return; + + int numberOfSpinners = categoriesSpinners.getChildCount(); + + if (numberOfSpinners < 3) return; + + String maybeSemester, maybeCourse; + + if (numberOfSpinners == 5) { + if (((AppCompatSpinnerWithoutDefault) categoriesSpinners.getChildAt(numberOfSpinners - 1)). + getSelectedItemPosition() == -1) { + maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 4)).getSelectedItem(); + maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem(); + } + else return; + } else if (numberOfSpinners == 4) { + maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 3)).getSelectedItem(); + maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem(); + } else { + maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem(); + maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) + categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem(); + } + + if (!maybeSemester.contains("εξάμηνο") && !maybeSemester.contains("Εξάμηνο")) return; + if (maybeCourse == null) return; + + String retrievedCourse = maybeCourse.replaceAll("-", "") + .replaceAll("\\((πρώην|πρωην).*\\)","") + .replace("(ΝΠΣ)", "") + .trim(); + + if(!retrievedCourse.isEmpty()){ + UploadsCourse foundUploadsCourse = UploadsCourse.findCourse(retrievedCourse, uploadsCourses); + + if(foundUploadsCourse != null){ + uploadsCourse = foundUploadsCourse; + semester = maybeSemester.replaceAll("-", "").trim().substring(0, 1); + Timber.d("Selected course: %s, semester: %s", uploadsCourse.getName(), semester); + generateFieldsButton.setEnabled(true); + } + } } } @@ -569,37 +1002,82 @@ public class UploadActivity extends BaseActivity { @Override protected void postExecution(ResultCode result) { updateUIElements(); - titleDescriptionBuilderButton.setEnabled(true); progressBar.setVisibility(ProgressBar.GONE); } } - private void updateUIElements() { - String[] tmpSpinnerArray = new String[uploadRootCategories.size()]; - for (int i = 0; i < uploadRootCategories.size(); ++i) { - tmpSpinnerArray[i] = uploadRootCategories.get(i).getCategoryTitle(); + public class ZipTask extends AsyncTask { + // Weak references will still allow the Activity to be garbage-collected + private final WeakReference weakActivity; + final String zipFilename, categorySelected, uploadTitleText, uploadDescriptionText, + fileIcon, uploaderProfileIndex; + Uri zipFileUri; + + // Suppresses default constructor + @SuppressWarnings("unused") + private ZipTask() { + weakActivity = null; + this.zipFilename = null; + this.categorySelected = null; + this.uploadTitleText = null; + this.uploadDescriptionText = null; + this.fileIcon = null; + this.uploaderProfileIndex = null; } - ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(BaseApplication.getInstance().getApplicationContext(), - R.layout.spinner_item, tmpSpinnerArray); - spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item); - rootCategorySpinner.setAdapter(spinnerArrayAdapter); + ZipTask(Activity uploadsActivity, @NonNull String zipFilename, + @NonNull String categorySelected, @NonNull String uploadTitleText, + @NonNull String uploadDescriptionText, @NonNull String fileIcon, + @NonNull String uploaderProfileIndex) { + weakActivity = new WeakReference<>(uploadsActivity); + this.zipFilename = zipFilename; + this.categorySelected = categorySelected; + this.uploadTitleText = uploadTitleText; + this.uploadDescriptionText = uploadDescriptionText; + this.fileIcon = fileIcon; + this.uploaderProfileIndex = uploaderProfileIndex; + } - //Sets bundle selection - if (bundleCategory != null) { - int bundleSelectionIndex = -1, currentIndex = 0; + @Override + protected void onPreExecute() { + assert weakActivity != null; + Toast.makeText(weakActivity.get(), "Zipping files", Toast.LENGTH_SHORT).show(); + } - for (UploadCategory category : uploadRootCategories) { - if (bundleCategory.get(0).contains(category.getCategoryTitle())) { - bundleSelectionIndex = currentIndex; - break; - } - ++currentIndex; + @Override + protected Boolean doInBackground(Uri... filesToZip) { + if (weakActivity == null || zipFilename == null) + return false; + + File zipFile = UploadsHelper.createZipFile(zipFilename); + + if (zipFile == null) + return false; + + zipFileUri = FileProvider.getUriForFile(weakActivity.get(), + weakActivity.get().getPackageName() + + ".provider", zipFile); + + UploadsHelper.zip(weakActivity.get(), filesToZip, zipFileUri); + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + if (weakActivity == null) + return; + + if (!result) { + Toast.makeText(weakActivity.get(), "Couldn't create zip!", Toast.LENGTH_SHORT).show(); + return; } - if (bundleSelectionIndex != -1) { - rootCategorySpinner.setSelection(bundleSelectionIndex, true); - bundleCategory.remove(0); + String uploadID = UUID.randomUUID().toString(); + if (!uploadFile(weakActivity.get(), uploadID, + getConfigForUpload(weakActivity.get(), uploadID, zipFilename), categorySelected, + uploadTitleText, uploadDescriptionText, fileIcon, uploaderProfileIndex, + zipFileUri)) { + Toast.makeText(weakActivity.get(), "Couldn't initiate upload.", Toast.LENGTH_SHORT).show(); } } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java index f9fd9407..f7eb6e0d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java @@ -11,23 +11,27 @@ import android.widget.LinearLayout; import android.widget.RadioGroup; import android.widget.Toast; +import androidx.annotation.Nullable; + import java.util.Calendar; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseActivity; import timber.log.Timber; -public class UploadFieldsBuilderActivity extends AppCompatActivity { - static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE = "UPLOAD_FIELD_BUILDER_COURSE"; +public class UploadFieldsBuilderActivity extends BaseActivity { + + static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_NAME = "BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_NAME"; + static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_MINIFIED_NAME = "BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_MINIFIED_NAME"; + static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_GREEKLISH_NAME = "BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_GREEKLISH_NAME"; static final String BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER = "UPLOAD_FIELD_BUILDER_SEMESTER"; static final String RESULT_FILENAME = "RESULT_FILENAME"; static final String RESULT_TITLE = "RESULT_TITLE"; static final String RESULT_DESCRIPTION = "RESULT_DESCRIPTION"; - private String course, semester; + private String courseName, courseMinifiedName, courseGreeklishName, semester; + private boolean isValidYear; private LinearLayout semesterChooserLinear; private RadioGroup typeRadio, semesterRadio; @@ -38,32 +42,26 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String working = s.toString(); - boolean isValid; if (working.length() == 4) { int currentYear = Calendar.getInstance().get(Calendar.YEAR); int inputYear = Integer.parseInt(working); - isValid = inputYear <= currentYear && inputYear > 2000; - } else { - isValid = false; - } + isValidYear = inputYear <= currentYear && inputYear > 1980; + } else + isValidYear = false; - if (!isValid) { + if (!isValidYear) year.setError("Please enter a valid year"); - } else { + else year.setError(null); - } - } @Override - public void afterTextChanged(Editable s) { - } + public void afterTextChanged(Editable s) { } @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } }; @Override @@ -73,9 +71,11 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { Bundle extras = getIntent().getExtras(); if (extras != null) { - course = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE); + courseName = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_NAME); + courseMinifiedName = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_MINIFIED_NAME); + courseGreeklishName = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE_GREEKLISH_NAME); semester = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER); - if (course == null || course.equals("") || semester == null || semester.equals("")) { + if (courseName == null || courseName.equals("") || semester == null || semester.equals("")) { Toast.makeText(this, "Something went wrong!", Toast.LENGTH_SHORT).show(); Timber.e("Bundle came empty in %s", UploadFieldsBuilderActivity.class.getSimpleName()); @@ -86,7 +86,7 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { } //Initialize toolbar - Toolbar toolbar = findViewById(R.id.toolbar); + toolbar = findViewById(R.id.toolbar); toolbar.setTitle(R.string.upload_fields_builder_toolbar_title); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { @@ -94,6 +94,9 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); } + createDrawer(); + drawer.setSelection(UPLOAD_ID, false); + semesterChooserLinear = findViewById(R.id.upload_fields_builder_choose_semester); semesterRadio = findViewById(R.id.upload_fields_builder_semester_radio_group); semesterRadio.check(Integer.parseInt(semester) % 2 == 0 @@ -121,8 +124,8 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { } else if (semesterChooserLinear.getVisibility() == View.VISIBLE && semesterId == -1) { Toast.makeText(view.getContext(), "Please choose a semester for the upload", Toast.LENGTH_SHORT).show(); return; - } else if (year.getText().toString().isEmpty()) { - Toast.makeText(view.getContext(), "Please choose a year for the upload", Toast.LENGTH_SHORT).show(); + } else if (year.getText().toString().isEmpty() || !isValidYear) { + Toast.makeText(view.getContext(), "Please choose a valid year for the upload", Toast.LENGTH_SHORT).show(); return; } @@ -139,11 +142,11 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { private String buildFilename() { switch (typeRadio.getCheckedRadioButtonId()) { case R.id.upload_fields_builder_radio_button_exams: - return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString(); + return courseGreeklishName + "_" + getGreeklishPeriod() + "_" + year.getText().toString(); case R.id.upload_fields_builder_radio_button_exam_solutions: - return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString() + "_Lyseis"; + return courseGreeklishName + "_" + getGreeklishPeriod() + "_" + year.getText().toString() + "_Lyseis"; case R.id.upload_fields_builder_radio_button_notes: - return getGreeklishCourseName() + "_" + year.getText().toString() + "_Shmeiwseis"; + return courseGreeklishName + "_" + year.getText().toString() + "_Simeioseis"; default: return null; } @@ -153,11 +156,11 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { private String buildTitle() { switch (typeRadio.getCheckedRadioButtonId()) { case R.id.upload_fields_builder_radio_button_exams: - return getMinifiedCourseName() + " - " + "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString(); + return "[" + courseMinifiedName + "] - " + "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString(); case R.id.upload_fields_builder_radio_button_exam_solutions: - return getMinifiedCourseName() + " - " + "Λύσεις θεμάτων " + getPeriod() + " " + year.getText().toString(); + return "[" + courseMinifiedName + "] - " + "Λύσεις θεμάτων " + getPeriod() + " " + year.getText().toString(); case R.id.upload_fields_builder_radio_button_notes: - return getMinifiedCourseName() + " - " + "Σημειώσεις παραδόσεων " + year.getText().toString(); + return "[" + courseMinifiedName + "] - " + "Σημειώσεις παραδόσεων " + year.getText().toString(); default: return null; } @@ -166,11 +169,11 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { private String buildDescription() { switch (typeRadio.getCheckedRadioButtonId()) { case R.id.upload_fields_builder_radio_button_exams: - return "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\""; + return "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + courseName + "\""; case R.id.upload_fields_builder_radio_button_exam_solutions: - return "Λύσεις των θεμάτων των εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\""; + return "Λύσεις των θεμάτων των εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + courseName + "\""; case R.id.upload_fields_builder_radio_button_notes: - return "Σημειώσεις των παραδόσεων του μαθήματος \"" + course + "\" από το " + year.getText().toString(); + return "Σημειώσεις των παραδόσεων του μαθήματος \"" + courseName + "\" από το " + year.getText().toString(); default: return null; } @@ -201,316 +204,4 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity { return null; } } - - @Nullable - private String getGreeklishCourseName() { - return getGreeklishOrMinifiedCourseName(true); - } - - @Nullable - private String getMinifiedCourseName() { - return getGreeklishOrMinifiedCourseName(false); - } - - - @Nullable - private String getGreeklishOrMinifiedCourseName(boolean greeklish) { - if (course.contains("Ψηφιακή Επεξεργασία Σήματος")) { - return greeklish ? "PSES" : "ΨΕΣ"; - } else if (course.contains("Ψηφιακή Επεξεργασία Εικόνας")) { - return greeklish ? "psee" : "ΨΕΕ"; - } else if (course.contains("Ψηφιακές Τηλεπικοινωνίες ΙΙ")) { - return greeklish ? "pshf_thlep_II" : "Ψηφιακές Τηλεπ. 2"; - } else if (course.contains("Ψηφιακές Τηλεπικοινωνίες Ι")) { - return greeklish ? "pshf_thlep_I" : "Ψηφιακές Τηλεπ. 1"; - } else if (course.contains("Ψηφιακά Φίλτρα")) { - return greeklish ? "filtra" : "Φίλτρα"; - } else if (course.contains("Ψηφιακά Συστήματα ΙΙΙ")) { - return greeklish ? "pshfiaka_III" : "Ψηφιακά 3"; - } else if (course.contains("Ψηφιακά Συστήματα ΙΙ")) { - return greeklish ? "pshfiaka_II" : "Ψηφιακά 2"; - } else if (course.contains("Ψηφιακά Συστήματα Ι")) { - return greeklish ? "pshfiaka_I" : "Ψηφιακά 1"; - } else if (course.contains("Φωτονική Τεχνολογία")) { - return greeklish ? "fwtonikh" : "Φωτονική"; - } else if (course.contains("Φυσική Ι")) { - return greeklish ? "fysikh_I" : "Φυσική 1"; - } else if (course.contains("Υψηλές Τάσεις ΙΙΙ")) { - return greeklish ? "ypshles_III" : "Υψηλές 3"; - } else if (course.contains("Υψηλές Τάσεις ΙΙ")) { - return greeklish ? "ypshles_II" : "Υψηλές 2"; - } else if (course.contains("Υψηλές Τάσεις Ι")) { - return greeklish ? "ypshles_I" : "Υψηλές 1"; - } else if (course.contains("Υψηλές Τάσεις 4")) { - return greeklish ? "ypshles_IV" : "Υψηλές 4"; - } else if (course.contains("Υπολογιστικός Ηλεκτρομαγνητισμός")) { - return greeklish ? "ypologistikos_HM" : "Υπολογιστικός Η/Μ"; - } else if (course.contains("Υπολογιστικές Μέθοδοι στα Ενεργειακά Συστήματα")) { - return greeklish ? "ymes" : "ΥΜΕΣ"; - } else if (course.contains("Τηλεπικοινωνιακή Ηλεκτρονική")) { - return greeklish ? "tilep_ilektr" : "Τηλεπ. Ηλεκτρ."; - } else if (course.contains("Τηλεοπτικά Συστήματα")) { - return greeklish ? "tileoptika" : "Τηλεοπτικά"; - } else if (course.contains("Τεχνολογία Λογισμικού")) { - return greeklish ? "SE" : "Τεχνολογία Λογισμικού"; - } else if (course.contains("Τεχνολογία Ηλεκτροτεχνικών Υλικών")) { - return greeklish ? "Hlektrotexnika_Ylika" : "Ηλεκτροτεχνικά Υλικά"; - } else if (course.contains("Τεχνολογία Ήχου και Εικόνας")) { - return greeklish ? "texn_hxoy_eikonas" : "Τεχνολογία Ήχου και Εικόνας"; - } else if (course.contains("Τεχνική Μηχανική")) { - return greeklish ? "texn_mhxan" : "Τεχν. Μηχαν."; - } else if (course.contains("Τεχνικές μη Καταστρεπτικών Δοκιμών")) { - return greeklish ? "non_destructive_tests" : "Μη Καταστρεπτικές Δοκιμές"; - } else if (course.contains("Τεχνικές Σχεδίασης με Η/Υ")) { - return greeklish ? "sxedio" : "Σχέδιο"; - } else if (course.contains("Τεχνικές Κωδικοποίησης")) { - return greeklish ? "texn_kwdikopoihshs" : "Τεχνικές Κωδικοποίησης"; - } else if (course.contains("Τεχνικές Βελτιστοποίησης")) { - return greeklish ? "veltistopoihsh" : "Βελτιστοποίηση"; - } else if (course.contains("Σύνθεση Τηλεπικοινωνιακών Διατάξεων")) { - return greeklish ? "synth_thlep_diataksewn" : "Σύνθεση Τηλεπ. Διατάξεων"; - } else if (course.contains("Σύνθεση Ενεργών και Παθητικών Κυκλωμάτων")) { - return greeklish ? "synthesh" : "Σύνθεση"; - } else if (course.contains("Σχεδίαση Συστημάτων VLSI")) { - return greeklish ? "VLSI" : "VLSI"; - } else if (course.contains("Συστήματα Υπολογιστών (Υπολογιστικά Συστήματα)")) { - return greeklish ? "sys_ypologistwn" : "Συσ. Υπολογιστών"; - } else if (course.contains("Συστήματα Πολυμέσων και Εικονική Πραγματικότητα")) { - return greeklish ? "polymesa" : "Πολυμέσα"; - } else if (course.contains("Συστήματα Μικροϋπολογιστών")) { - return greeklish ? "mikro_I" : "Μίκρο 1"; - } else if (course.contains("Συστήματα Ηλεκτροκίνησης")) { - return greeklish ? "hlektrokinhsh" : "Ηλεκτροκίνηση"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙΙ")) { - return greeklish ? "SHE_III" : "ΣΗΕ 3"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙ")) { - return greeklish ? "SHE_II" : "ΣΗΕ 2"; - } else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας Ι")) { - return greeklish ? "SHE_I" : "ΣΗΕ 1"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙI")) { - return greeklish ? "SAE_III" : "ΣΑΕ 3"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙ")) { - return greeklish ? "SAE_II" : "ΣΑΕ 2"; - } else if (course.contains("Συστήματα Αυτομάτου Ελέγχου Ι")) { - return greeklish ? "SAE_1" : "ΣΑΕ 1"; - } else if (course.contains("Στοχαστικό Σήμα")) { - return greeklish ? "stox_shma" : "Στοχ. Σήμα"; - } else if (course.contains("Σταθμοί Παραγωγής Ηλεκτρικής Ενέργειας")) { - return greeklish ? "SPHE" : "ΣΠΗΕ"; - } else if (course.contains("Σερβοκινητήρια Συστήματα")) { - return greeklish ? "servo" : "Σέρβο"; - } else if (course.contains("Σήματα και Συστήματα")) { - return greeklish ? "analog_shma" : "Σύματα & Συστήματα"; - } else if (course.contains("Ρομποτική")) { - return greeklish ? "rompotikh" : "Ρομποτική"; - } else if (course.contains("Προσομοίωση και Μοντελοποίηση Συστημάτων")) { - return greeklish ? "montelopoihsh" : "Μοντελοποίηση"; - } else if (course.contains("Προηγμένες Τεχνικές Επεξεργασίας Σήματος")) { - return greeklish ? "ptes" : "ΠΤΕΣ"; - } else if (course.contains("Προγραμματιστικές Τεχνικές")) { - return greeklish ? "cpp" : "Προγραμματ. Τεχν."; - } else if (course.contains("Προγραμματιζόμενα Κυκλώματα ASIC")) { - return greeklish ? "asic" : "ASIC"; - } else if (course.contains("Παράλληλα και Κατανεμημένα Συστήματα")) { - return greeklish ? "parallhla" : "Παράλληλα"; - } else if (course.contains("Οργάνωση και Διοίκηση Εργοστασίων")) { - return greeklish ? "organ_dioik_ergostasiwn" : "Οργάνωση και Διοίκηση Εργοστασίων"; - } else if (course.contains("Οργάνωση Υπολογιστών")) { - return greeklish ? "org_ypol" : "Οργάνωση Υπολ."; - } else if (course.contains("Οπτική ΙΙ")) { - return greeklish ? "optikh_II" : "Οπτική 2"; - } else if (course.contains("Οπτική Ι")) { - return greeklish ? "optikh_I" : "Οπτική 1"; - } else if (course.contains("Οπτικές Επικοινωνίες")) { - return greeklish ? "optikes_thlep" : "Οπτικές Τηλεπ."; - } else if (course.contains("Μικροκύματα II")) { - return greeklish ? "mikrokymata_II" : "Μικροκύματα 2"; - } else if (course.contains("Μικροκύματα I")) { - return greeklish ? "mikrokymata_I" : "Μικροκύματα 1"; - } else if (course.contains("Μικροκυματική Τηλεπισκόπηση")) { - return greeklish ? "thlepiskophsh" : "Τηλεπισκόπηση"; - } else if (course.contains("Μικροεπεξεργαστές και Περιφερειακά")) { - return greeklish ? "mikro_II" : "Μίκρο 2"; - } else if (course.contains("Μετάδοση Θερμότητας")) { - return greeklish ? "metadosi_therm" : "Μετάδοση Θερμ."; - } else if (course.contains("Λογισμός ΙΙ")) { - return greeklish ? "logismos_II" : "Λογισμός 2"; - } else if (course.contains("Λογισμός Ι")) { - return greeklish ? "logismos_I" : "Λογισμός 1"; - } else if (course.contains("Λογική Σχεδίαση")) { - return greeklish ? "logiki_sxediash" : "Λογική Σχεδίαση"; - } else if (course.contains("Λειτουργικά Συστήματα")) { - return greeklish ? "OS" : "Λειτουργικά"; - } else if (course.contains("Κινητές και Δορυφορικές Επικοινωνίες")) { - return greeklish ? "kinhtes_doryforikes_epik" : "Κινητές & Δορυφορικές Επικοινωνίες"; - } else if (course.contains("Κβαντική Φυσική")) { - return greeklish ? "kvantikh" : "Κβαντική"; - } else if (course.contains("Θεωρία και Τεχνολογία Πυρηνικών Αντιδραστήρων")) { - return greeklish ? "texn_antidrasthrwn" : "Τεχνολογία Αντιδραστήρων"; - } else if (course.contains("Θεωρία Υπολογισμών και Αλγορίθμων")) { - return greeklish ? "thya" : "ΘΥΑ"; - } else if (course.contains("Θεωρία Σκέδασης")) { - return greeklish ? "skedash" : "Σκέδαση"; - } else if (course.contains("Θεωρία Σημάτων και Γραμμικών Συστημάτων")) { - return greeklish ? "analog_shma" : "Σύματα & Συστήματα"; - } else if (course.contains("Θεωρία Πληροφοριών")) { - return greeklish ? "theoria_plir" : "Θεωρία Πληρ."; - } else if (course.contains("Θεωρία Πιθανοτήτων και Στατιστική")) { - return greeklish ? "pithanothtes" : "Πιθανότητες"; - } else if (course.contains("Ημιαγωγά Υλικά: Θεωρία-Διατάξεις")) { - return greeklish ? "Hmiagwga_Ylika" : "Ημιαγωγά Υλικά"; - } else if (course.contains("Ηλεκτρονική ΙΙΙ")) { - return greeklish ? "hlektronikh_III" : "Ηλεκτρονική 3"; - } else if (course.contains("Ηλεκτρονική ΙΙ")) { - return greeklish ? "hlektronikh_2" : "Ηλεκτρονική 2"; - } else if (course.contains("Ηλεκτρονική Ι")) { - return greeklish ? "hlektronikh_1" : "Ηλεκτρονική 1"; - } else if (course.contains("Ηλεκτρονικές Διατάξεις και Μετρήσεις")) { - return greeklish ? "hlektron_diatakseis_metrhseis" : "Ηλεκτρονικές Διατάξεις και Μετρήσεις"; - } else if (course.contains("Ηλεκτρονικά Ισχύος ΙΙ")) { - return greeklish ? "isxyos_II" : "Ισχύος 2"; - } else if (course.contains("Ηλεκτρονικά Ισχύος Ι")) { - return greeklish ? "isxyos_I" : "Ισχύος 1"; - } else if (course.contains("Ηλεκτρομαγνητικό Πεδίο ΙΙ")) { - return greeklish ? "pedio_II" : "Πεδίο 2"; - } else if (course.contains("Ηλεκτρομαγνητικό Πεδίο Ι")) { - return greeklish ? "pedio_I" : "Πεδίο 1"; - } else if (course.contains("Ηλεκτρομαγνητική Συμβατότητα")) { - return greeklish ? "HM_symvatothta" : "H/M Συμβατότητα"; - } else if (course.contains("Ηλεκτρολογικά Υλικά")) { - return greeklish ? "ylika" : "Ηλεκτρ. Υλικά"; - } else if (course.contains("Ηλεκτρική Οικονομία")) { - return greeklish ? "hlektr_oikonomia" : "Ηλεκτρική Οικονομία"; - } else if (course.contains("Ηλεκτρικές Μηχανές Γ'")) { - return greeklish ? "mhxanes_C" : "Μηχανές Γ"; - } else if (course.contains("Ηλεκτρικές Μηχανές Β'")) { - return greeklish ? "mhxanes_B" : "Μηχανές Β"; - } else if (course.contains("Ηλεκτρικές Μηχανές Α'")) { - return greeklish ? "mhxanes_A" : "Μηχανές Α"; - } else if (course.contains("Ηλεκτρικές Μετρήσεις ΙΙ")) { - return greeklish ? "metrhseis_II" : "Μετρήσεις 2"; - } else if (course.contains("Ηλεκτρικές Μετρήσεις Ι")) { - return greeklish ? "metrhseis_1" : "Μετρήσεις 1"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙΙ")) { - return greeklish ? "kyklwmata_I" : "Κυκλώματα 3"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙ")) { - return greeklish ? "kyklwmata_II" : "Κυκλώματα 2"; - } else if (course.contains("Ηλεκτρικά Κυκλώματα Ι")) { - return greeklish ? "kyklwmata_I" : "Κυκλώματα 1"; - } else if (course.contains("Ηλεκτρακουστική ΙΙ")) { - return greeklish ? "hlektroakoystikh_II" : "Ηλεκτροακουστική 2"; - } else if (course.contains("Ηλεκτρακουστική Ι")) { - return greeklish ? "hlektroakoystikh_I" : "Ηλεκτροακουστική 1"; - } else if (course.contains("Εφαρμοσμένη Θερμοδυναμική")) { - return greeklish ? "thermodynamikh" : "Θερμοδυναμική"; - } else if (course.contains("Εφαρμοσμένα Μαθηματικά ΙΙ")) { - return greeklish ? "efarmosmena_math_II" : "Εφαρμοσμένα 2"; - } else if (course.contains("Εφαρμοσμένα Μαθηματικά Ι")) { - return greeklish ? "efarmosmena_math_I" : "Εφαρμοσμένα 1"; - } else if (course.contains("Εφαρμογές Τηλεπικοινωνιακών Διατάξεων")) { - return greeklish ? "efarm_thlep_diataksewn" : "Εφαρμογές Τηλεπ. Διατάξεων"; - } else if (course.contains("Ευφυή Συστήματα Ρομπότ")) { - return greeklish ? "eufuh" : "Ευφυή"; - } else if (course.contains("Ευρυζωνικά Δίκτυα")) { - return greeklish ? "eyryzwnika" : "Ευρυζωνικά"; - } else if (course.contains("Επιχειρησιακή Έρευνα")) { - return greeklish ? "epixeirisiaki" : "Επιχειρησιακή Έρευνα"; - } else if (course.contains("Ενσωματωμένα Συστήματα Πραγματικού Χρόνου")) { - return greeklish ? "enswmatwmena" : "Ενσωματωμένα"; - } else if (course.contains("Εισαγωγή στις εφαρμογές Πυρηνικής Τεχνολογίας")) { - return greeklish ? "Intro_Purhnikh_Texn" : "Εισ. Πυρηνικη Τεχν."; - } else if (course.contains("Εισαγωγή στην Πολιτική Οικονομία")) { - return greeklish ? "polit_oik" : "Πολιτική Οικονομία"; - } else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία ΙΙ")) { - return greeklish ? "EET_2" : "ΕΕΤ2"; - } else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία Ι")) { - return greeklish ? "EET_I" : "ΕΕΤ 1"; - } else if (course.contains("Ειδικές Κεραίες, Σύνθεση Κεραιών")) { - return greeklish ? "eidikes_keraies" : "Ειδικές Κεραίες, Σύνθεση Κεραιών"; - } else if (course.contains("Ειδικές Αρχιτεκτονικές Υπολογιστών")) { - return greeklish ? "eidikes_arx_ypolog" : "Ειδικές Αρχιτεκτονικές Υπολογιστών"; - } else if (course.contains("Ειδικά Κεφάλαια Συστημάτων Ηλεκτρικής Ενέργειας")) { - return greeklish ? "ekshe" : "ΕΚΣΗΕ"; - } else if (course.contains("Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι")) { - return greeklish ? "eidika_kef_HM_pedioy_I" : "Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι"; - } else if (course.contains("Ειδικά Κεφάλαια Διαφορικών Εξισώσεων")) { - return greeklish ? "eidika_kef_diaf_eksis" : "Ειδικά Κεφάλαια Διαφορικών Εξισώσεων"; - } else if (course.contains("Δομημένος Προγραμματισμός")) { - return greeklish ? "C" : "Δομ. Προγραμμ."; - } else if (course.contains("Δομές Δεδομένων")) { - return greeklish ? "dom_dedomenwn" : "Δομ. Δεδομ."; - } else if (course.contains("Διαχείριση Συστημάτων Ηλεκτρικής Ενέργειας")) { - return greeklish ? "dshe" : "ΔΣΗΕ"; - } else if (course.contains("Διαφορικές Εξισώσεις")) { - return greeklish ? "diaforikes" : "Διαφορικές"; - } else if (course.contains("Διανεμημένη Παραγωγή")) { - return greeklish ? "dian_paragwgh" : "Διανεμημένη Παραγωγή"; - } else if (course.contains("Διακριτά μαθηματικά")) { - return greeklish ? "diakrita" : "Διακριτά Μαθηματικά"; - } else if (course.contains("Διακριτά Μαθηματικά")) { - return greeklish ? "diakrita" : "Διακριτά Μαθηματικά"; - } else if (course.contains("Διάδοση Ηλεκτρομαγνητικού Κύματος Ι (πρώην Πεδίο ΙΙΙ)")) { - return greeklish ? "diadosi_1" : "Διάδοση 1"; - } else if (course.contains("Διάδοση Η/Μ Κύματος ΙΙ")) { - return greeklish ? "diadosi_II" : "Διάδοση 2"; - } else if (course.contains("Δίκτυα Υπολογιστών ΙΙ")) { - return greeklish ? "diktya_II" : "Δίκτυα 2"; - } else if (course.contains("Δίκτυα Υπολογιστών Ι")) { - return greeklish ? "diktya_I" : "Δίκτυα 1"; - } else if (course.contains("Δίκτυα Τηλεπικοινωνιών")) { - return greeklish ? "diktya_thlep" : "Δίκτυα Τηλέπ."; - } else if (course.contains("Γραφική με Υπολογιστές")) { - return greeklish ? "grafikh" : "Γραφική"; - } else if (course.contains("Γραμμική Άλγεβρα")) { - return greeklish ? "grammikh_algebra" : "Γραμμ. Άλγεβρ."; - } else if (course.contains("Γεωηλεκτρομαγνητισμός")) { - return greeklish ? "geohlektromagnitismos" : "Γεωηλεκτρομαγνητισμός"; - } else if (course.contains("Βιοϊατρική Τεχνολογία")) { - return greeklish ? "vioiatriki" : "Βιοιατρική"; - } else if (course.contains("Βιομηχανική Πληροφορική")) { - return greeklish ? "viomix_plir" : "Βιομηχανική Πληρ"; - } else if (course.contains("Βιομηχανικά Ηλεκτρονικά")) { - return greeklish ? "bhomix_hlektronika" : "Βιομηχανικά Ηλεκτρονικά"; - } else if (course.contains("Βάσεις Δεδομένων")) { - return greeklish ? "vaseis" : "Βάσεις"; - } else if (course.contains("Ασύρματος Τηλεπικοινωνία ΙΙ")) { - return greeklish ? "asyrmatos_II" : "Ασύρματος 2"; - } else if (course.contains("Ασύρματος Τηλεπικοινωνία Ι")) { - return greeklish ? "asyrmatos_I" : "Ασύρματος 1"; - } else if (course.contains("Ασφάλεια Πληροφοριακών Συστημάτων")) { - return greeklish ? "asfaleia" : "Ασφάλεια"; - } else if (course.contains("Ασαφή Συστήματα")) { - return greeklish ? "asafh" : "Ασαφή"; - } else if (course.contains("Αρχιτεκτονική Υπολογιστών")) { - return greeklish ? "arx_ypologistwn" : "Αρχ. Υπολογιστών"; - } else if (course.contains("Αρχές Παράλληλης Επεξεργασίας")) { - return greeklish ? "arxes_parall_epeksergasias" : "Αρχές Παράλληλης Επεξεργασίας"; - } else if (course.contains("Αρχές Οικονομίας")) { - return greeklish ? "arx_oikonomias" : "Αρχές Οικονομίας"; - } else if (course.contains("Αριθμητική Ανάλυση")) { - return greeklish ? "arith_anal" : "Αριθμ. Ανάλυση"; - } else if (course.contains("Αξιοπιστία Συστημάτων")) { - return greeklish ? "aksiopistia_systhmatwn" : "Αξιοπιστία Συστημάτων"; - } else if (course.contains("Αντικειμενοστραφής Προγραμματισμός")) { - return greeklish ? "OOP" : "Αντικειμενοστραφής"; - } else if (course.contains("Αναλογικές Τηλεπικοινωνίες (πρώην Τηλεπικοινωνιακά Συστήματα Ι)")) { - return greeklish ? "anal_thlep" : "Αναλογικές Τηλεπ."; - } else if (course.contains("Αναγνώριση Προτύπων")) { - return greeklish ? "protipa" : "Αναγνώριση Προτύπων"; - } else if (course.contains("Ανάλυση και Σχεδίαση Αλγορίθμων")) { - return greeklish ? "algorithms" : "Αλγόριθμοι"; - } else if (course.contains("Ανάλυση Χρονοσειρών")) { - return greeklish ? "xronoseires" : "Χρονοσειρές"; - } else if (course.contains("Ανάλυση Συστημάτων Ηλεκτρικής Ενέργειας")) { - return greeklish ? "ASHE" : "ΑΣΗΕ"; - } else if (course.contains("Ανάλυση Ηλεκτρικών Κυκλωμάτων με Υπολογιστή")) { - return greeklish ? "analysh_hlektr_kykl" : "Ανάλυση Ηλεκτρικ. Κυκλ. με Υπολογιστή"; - } else if (course.contains("Ακουστική ΙΙ")) { - return greeklish ? "akoystikh_II" : "Ακουστική 2"; - } else if (course.contains("Ακουστική Ι")) { - return greeklish ? "akoystikh_I" : "Ακουστική 1"; - } else { - return null; - } - } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsCourse.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsCourse.java new file mode 100644 index 00000000..12f0475f --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsCourse.java @@ -0,0 +1,79 @@ +package gr.thmmy.mthmmy.activities.upload; + +import android.os.Bundle; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import gr.thmmy.mthmmy.base.BaseApplication; +import timber.log.Timber; + +class UploadsCourse { + private String name; + private String minifiedName; + private String greeklishName; + + private UploadsCourse(String fullName, String minifiedName, String greeklishName) { + this.name = fullName; + this.minifiedName = minifiedName; + this.greeklishName = greeklishName; + } + + String getName() { + return name; + } + + String getMinifiedName() { + return minifiedName; + } + + String getGreeklishName() { + return greeklishName; + } + + static Map generateUploadsCourses(String[] uploadsCoursesRes){ + Map uploadsCourses = new HashMap<>(); + for(String uploadsCourseStr:uploadsCoursesRes) { + String[] split = uploadsCourseStr.split(":"); + UploadsCourse uploadsCourse = new UploadsCourse(split[0], split[1], split[2]); + uploadsCourses.put(uploadsCourse.getName(),uploadsCourse); + } + return uploadsCourses; + } + + static UploadsCourse findCourse(String retrievedCourse, + Map uploadsCourses){ + retrievedCourse = normalizeGreekNumbers(retrievedCourse); + UploadsCourse uploadsCourse = uploadsCourses.get(retrievedCourse); + if(uploadsCourse != null) return uploadsCourse; + + String foundKey = null; + for (Map.Entry entry : uploadsCourses.entrySet()) { + String key = entry.getKey(); + if ((key.contains(retrievedCourse) || retrievedCourse.contains(key)) + && (foundKey==null || key.length()>foundKey.length())) + foundKey = key; + } + + if(foundKey==null){ + Timber.w("Couldn't find course that matches %s", retrievedCourse); + Bundle bundle = new Bundle(); + bundle.putString("course_name", retrievedCourse); + BaseApplication.getInstance().logFirebaseAnalyticsEvent("unsupported_uploads_course", bundle); + return null; + } + + return uploadsCourses.get(foundKey); + } + + private static String normalizeGreekNumbers(String stringWithGreekNumbers) { + StringBuilder normalizedStrBuilder = new StringBuilder(stringWithGreekNumbers); + Pattern pattern = Pattern.compile("(Ι+)(?:\\s|\\(|\\)|$)"); + Matcher matcher = pattern.matcher(stringWithGreekNumbers); + while (matcher.find()) + normalizedStrBuilder.replace(matcher.start(1), matcher.end(1), matcher.group(1).replaceAll("Ι", "I")); + return normalizedStrBuilder.toString(); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java index e5f5301b..92b71cb0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java @@ -1,71 +1,48 @@ package gr.thmmy.mthmmy.activities.upload; import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; -import android.provider.OpenableColumns; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.FileProvider; + +import com.snatik.storage.Storage; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import gr.thmmy.mthmmy.utils.FileUtils; import timber.log.Timber; -class UploadsHelper { - private static final int DEFAULT_MIN_WIDTH_QUALITY = 400; - private static final String CACHE_IMAGE_NAME = "tempUploadFile.jpg"; - - @NonNull - static String filenameFromUri(Context context, Uri uri) { - String filename = null; - if (uri.getScheme().equals("content")) { - try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - } - } - } - if (filename == null) { - filename = uri.getPath(); - int cut = filename.lastIndexOf('/'); - if (cut != -1) { - filename = filename.substring(cut + 1); - } - } - - return filename; - } +public class UploadsHelper { + private static final int BUFFER = 4096; + private static final String TEMP_FILES_DIRECTORY = "~tmp_mTHMMY_uploads"; @SuppressWarnings("ResultOfMethodCallIgnored") @Nullable - static String createTempFile(Context context, Uri fileUri, String newFilename) { - String oldFilename = filenameFromUri(context, fileUri); - String fileExtension = oldFilename.substring(oldFilename.indexOf(".")); + static Uri createTempFile(Context context, Storage storage, Uri fileUri, String newFilename) { + String oldFilename = FileUtils.filenameFromUri(context, fileUri); + String fileExtension = oldFilename.substring(oldFilename.indexOf('.')); String destinationFilename = Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension; + File.separatorChar + TEMP_FILES_DIRECTORY + File.separatorChar + newFilename + fileExtension; File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads"); + File.separatorChar + TEMP_FILES_DIRECTORY); - if (!tempDirectory.exists()) { - if (!tempDirectory.mkdirs()) { - Timber.w("Temporary directory build returned false in %s", UploadActivity.class.getSimpleName()); - Toast.makeText(context, "Couldn't create temporary directory", Toast.LENGTH_SHORT).show(); - return null; - } + if (!tempDirectory.exists() && !tempDirectory.mkdirs()) { + Timber.w("Temporary directory build returned false in %s", UploadActivity.class.getSimpleName()); + Toast.makeText(context, "Couldn't create temporary directory", Toast.LENGTH_SHORT).show(); + return null; } InputStream inputStream; @@ -97,103 +74,63 @@ class UploadsHelper { } } - return destinationFilename; - } + return FileProvider.getUriForFile(context, context.getPackageName() + + ".provider", storage.getFile(destinationFilename)); - static File getCacheFile(Context context) { - File imageFile = new File(context.getExternalCacheDir(), CACHE_IMAGE_NAME); - //noinspection ResultOfMethodCallIgnored - imageFile.getParentFile().mkdirs(); - return imageFile; } - @SuppressWarnings("ResultOfMethodCallIgnored") - static void deleteTempFiles() { - File tempFilesDirectory = new File(Environment.getExternalStorageDirectory().getPath() + - File.separatorChar + "~tmp_mThmmy_uploads"); - - if (tempFilesDirectory.isDirectory()) { - String[] tempFilesArray = tempFilesDirectory.list(); - for (String tempFile : tempFilesArray) { - new File(tempFilesDirectory, tempFile).delete(); - } - tempFilesDirectory.delete(); - } - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - static void deleteCacheFiles(Context context) { - File cacheFilesDirectory = context.getExternalCacheDir(); - assert cacheFilesDirectory != null; - String[] tempFilesArray = cacheFilesDirectory.list(); - for (String tempFile : tempFilesArray) { - new File(cacheFilesDirectory, tempFile).delete(); - } - } - - /** - * Resize to avoid using too much memory loading big images (e.g.: 2560*1920) - **/ - static Bitmap getImageResized(Context context, Uri selectedImage) { - Bitmap bm; - int[] sampleSizes = new int[]{5, 3, 2, 1}; - int i = 0; - do { - bm = decodeBitmap(context, selectedImage, sampleSizes[i]); - i++; - } while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length); - return bm; - } - - private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = sampleSize; + @Nullable + static File createZipFile(@NonNull String zipFilename) { + // Create a zip file name + File zipFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + + File.separator + "mTHMMY"); - AssetFileDescriptor fileDescriptor = null; - try { - fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r"); - } catch (FileNotFoundException e) { - e.printStackTrace(); + if (!zipFolder.exists() && !zipFolder.mkdirs()) { + Timber.w("Zip folder build returned false in %s", UploadsHelper.class.getSimpleName()); + return null; } - assert fileDescriptor != null; - return BitmapFactory.decodeFileDescriptor( - fileDescriptor.getFileDescriptor(), null, options); + return new File(zipFolder, zipFilename); } - static int getRotation(Context context, Uri imageUri) { - int rotation = 0; + static void zip(Context context, Uri[] files, Uri zipFile) { try { - - context.getContentResolver().notifyChange(imageUri, null); - ExifInterface exif = new ExifInterface(imageUri.getPath()); - int orientation = exif.getAttributeInt( - ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_NORMAL); - - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_270: - rotation = 270; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - rotation = 180; - break; - case ExifInterface.ORIENTATION_ROTATE_90: - rotation = 90; - break; + BufferedInputStream origin; + OutputStream dest = context.getContentResolver().openOutputStream(zipFile); + assert dest != null; + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); + byte[] data = new byte[BUFFER]; + + for (Uri file : files) { + InputStream inputStream = context.getContentResolver().openInputStream(file); + assert inputStream != null; + origin = new BufferedInputStream(inputStream, BUFFER); + + ZipEntry entry = new ZipEntry(FileUtils.filenameFromUri(context, file)); + out.putNextEntry(entry); + int count; + + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + } + origin.close(); } + + out.close(); } catch (Exception e) { e.printStackTrace(); } - return rotation; } - static Bitmap rotate(Bitmap bm, int rotation) { - if (rotation != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(rotation); - return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + public static void deleteTempFiles(Storage storage) { + File tempFilesDirectory = new File(Environment.getExternalStorageDirectory().getPath() + + File.separatorChar + TEMP_FILES_DIRECTORY); + + if (storage.isDirectoryExists(tempFilesDirectory.getAbsolutePath())) { + for (File tempFile : storage.getFiles(tempFilesDirectory.getAbsolutePath())) { + storage.deleteFile(tempFile.getAbsolutePath()); + } + storage.deleteDirectory(tempFilesDirectory.getAbsolutePath()); } - return bm; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadException.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadException.java new file mode 100644 index 00000000..0b8cdc29 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadException.java @@ -0,0 +1,8 @@ +package gr.thmmy.mthmmy.activities.upload.multipart; + +public class MultipartUploadException extends RuntimeException { + public MultipartUploadException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadRequest.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadRequest.java new file mode 100644 index 00000000..f6126523 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadRequest.java @@ -0,0 +1,99 @@ +package gr.thmmy.mthmmy.activities.upload.multipart; + +import android.content.Context; +import android.content.Intent; + +import net.gotev.uploadservice.HttpUploadRequest; +import net.gotev.uploadservice.Logger; +import net.gotev.uploadservice.UploadFile; +import net.gotev.uploadservice.UploadTask; + +import java.io.FileNotFoundException; +import java.net.MalformedURLException; + +/** + From MultipartUploadRequest gotev/android-upload-service in order to use the local custom + MultipartUploadTask. + */ +public class MultipartUploadRequest extends HttpUploadRequest { + + private static final String LOG_TAG = MultipartUploadRequest.class.getSimpleName(); + private boolean isUtf8Charset = false; + + + public MultipartUploadRequest(final Context context, final String uploadId, final String serverUrl) + throws IllegalArgumentException, MalformedURLException { + super(context, uploadId, serverUrl); + } + + public MultipartUploadRequest(final Context context, final String serverUrl) + throws MalformedURLException, IllegalArgumentException { + this(context, null, serverUrl); + } + + @Override + protected void initializeIntent(Intent intent) { + super.initializeIntent(intent); + intent.putExtra(MultipartUploadTask.PARAM_UTF8_CHARSET, isUtf8Charset); + } + + @Override + protected Class getTaskClass() { + return MultipartUploadTask.class; + } + + public MultipartUploadRequest addFileToUpload(String filePath, + String parameterName, + String fileName, String contentType) + throws FileNotFoundException, IllegalArgumentException { + + UploadFile file = new UploadFile(filePath); + filePath = file.getPath(); + + if (parameterName == null || "".equals(parameterName)) { + throw new IllegalArgumentException("Please specify parameterName value for file: " + + filePath); + } + + file.setProperty(MultipartUploadTask.PROPERTY_PARAM_NAME, parameterName); + + if (contentType == null || contentType.isEmpty()) { + contentType = file.getContentType(context); + Logger.debug(LOG_TAG, "Auto-detected MIME type for " + filePath + + " is: " + contentType); + } else { + Logger.debug(LOG_TAG, "Content Type set for " + filePath + + " is: " + contentType); + } + + file.setProperty(MultipartUploadTask.PROPERTY_CONTENT_TYPE, contentType); + + if (fileName == null || "".equals(fileName)) { + fileName = file.getName(context); + Logger.debug(LOG_TAG, "Using original file name: " + fileName); + } else { + Logger.debug(LOG_TAG, "Using custom file name: " + fileName); + } + + file.setProperty(MultipartUploadTask.PROPERTY_REMOTE_FILE_NAME, fileName); + + params.files.add(file); + return this; + } + + public MultipartUploadRequest addFileToUpload(final String path, final String parameterName, + final String fileName) + throws FileNotFoundException, IllegalArgumentException { + return addFileToUpload(path, parameterName, fileName, null); + } + + public MultipartUploadRequest addFileToUpload(final String path, final String parameterName) + throws FileNotFoundException, IllegalArgumentException { + return addFileToUpload(path, parameterName, null, null); + } + + public MultipartUploadRequest setUtf8Charset() { + isUtf8Charset = true; + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadTask.java new file mode 100644 index 00000000..be9bb0f6 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/multipart/MultipartUploadTask.java @@ -0,0 +1,160 @@ +package gr.thmmy.mthmmy.activities.upload.multipart; + +import android.content.Intent; + +import net.gotev.uploadservice.HttpUploadTask; +import net.gotev.uploadservice.NameValue; +import net.gotev.uploadservice.UploadFile; +import net.gotev.uploadservice.UploadService; +import net.gotev.uploadservice.http.BodyWriter; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + Extended MultipartUploadTask from gotev/android-upload-service to include a fix for the parameter + tp_dluploadpic. Also changed Connection to keep-alive. + */ +public class MultipartUploadTask extends HttpUploadTask { + + static final String PARAM_UTF8_CHARSET = "multipartUtf8Charset"; + + private static final String BOUNDARY_SIGNATURE = "-------AndroidUploadService"; + private static final Charset US_ASCII = Charset.forName("US-ASCII"); + private static final String NEW_LINE = "\r\n"; + private static final String TWO_HYPHENS = "--"; + + // properties associated to each file + static final String PROPERTY_REMOTE_FILE_NAME = "httpRemoteFileName"; + static final String PROPERTY_CONTENT_TYPE = "httpContentType"; + static final String PROPERTY_PARAM_NAME = "httpParamName"; + + private byte[] boundaryBytes; + private byte[] trailerBytes; + private Charset charset; + + @Override + protected void init(UploadService service, Intent intent) throws IOException { + super.init(service, intent); + + String boundary = BOUNDARY_SIGNATURE + System.nanoTime(); + boundaryBytes = (TWO_HYPHENS + boundary + NEW_LINE).getBytes(US_ASCII); + trailerBytes = (TWO_HYPHENS + boundary + TWO_HYPHENS + NEW_LINE).getBytes(US_ASCII); + charset = intent.getBooleanExtra(PARAM_UTF8_CHARSET, false) ? + Charset.forName("UTF-8") : US_ASCII; + + httpParams.addHeader("Connection", "Keep-Alive"); + httpParams.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + } + + @Override + protected long getBodyLength() throws UnsupportedEncodingException { + return (getRequestParametersLength() + getFilesLength() + trailerBytes.length); + } + + @Override + public void onBodyReady(BodyWriter bodyWriter) throws IOException { + //reset uploaded bytes when the body is ready to be written + //because sometimes this gets invoked when network changes + uploadedBytes = 0; + writeRequestParameters(bodyWriter); + writeFiles(bodyWriter); + bodyWriter.write(trailerBytes); + uploadedBytes += trailerBytes.length; + broadcastProgress(uploadedBytes, totalBytes); + } + + private long getFilesLength() throws UnsupportedEncodingException { + long total = 0; + + for (UploadFile file : params.files) { + total += getTotalMultipartBytes(file); + } + + return total; + } + + private long getRequestParametersLength() throws UnsupportedEncodingException { + long parametersBytes = 0; + + if (!httpParams.getRequestParameters().isEmpty()) { + for (final NameValue parameter : httpParams.getRequestParameters()) { + // the bytes needed for every parameter are the sum of the boundary bytes + // and the bytes occupied by the parameter + parametersBytes += boundaryBytes.length + getMultipartBytes(parameter).length; + } + } + + return parametersBytes; + } + + private byte[] getMultipartBytes(NameValue parameter) throws UnsupportedEncodingException { + if(parameter.getName().equals("tp_dluploadpic")){ + String header = "Content-Disposition: form-data; name=\"" + + parameter.getName() + "\"; filename=\"\"" + NEW_LINE + + "Content-Type: application/octet-stream" + NEW_LINE + NEW_LINE; + return header.getBytes(charset); + } + else + return ("Content-Disposition: form-data; name=\"" + parameter.getName() + "\"" + + NEW_LINE + NEW_LINE + parameter.getValue() + NEW_LINE).getBytes(charset); + } + + private byte[] getMultipartHeader(UploadFile file) + throws UnsupportedEncodingException { + String header = "Content-Disposition: form-data; name=\"" + + file.getProperty(PROPERTY_PARAM_NAME) + "\"; filename=\"" + + file.getProperty(PROPERTY_REMOTE_FILE_NAME) + "\"" + NEW_LINE + + "Content-Type: " + file.getProperty(PROPERTY_CONTENT_TYPE) + + NEW_LINE + NEW_LINE; + + return header.getBytes(charset); + } + + private long getTotalMultipartBytes(UploadFile file) + throws UnsupportedEncodingException { + return boundaryBytes.length + getMultipartHeader(file).length + file.length(service) + + NEW_LINE.getBytes(charset).length; + } + + private void writeRequestParameters(BodyWriter bodyWriter) throws IOException { + if (!httpParams.getRequestParameters().isEmpty()) { + for (final NameValue parameter : httpParams.getRequestParameters()) { + bodyWriter.write(boundaryBytes); + byte[] formItemBytes = getMultipartBytes(parameter); + bodyWriter.write(formItemBytes); + + uploadedBytes += boundaryBytes.length + formItemBytes.length; + broadcastProgress(uploadedBytes, totalBytes); + } + } + } + + private void writeFiles(BodyWriter bodyWriter) throws IOException { + for (UploadFile file : params.files) { + if (!shouldContinue) + break; + + bodyWriter.write(boundaryBytes); + byte[] headerBytes = getMultipartHeader(file); + bodyWriter.write(headerBytes); + + uploadedBytes += boundaryBytes.length + headerBytes.length; + broadcastProgress(uploadedBytes, totalBytes); + + bodyWriter.writeStream(file.getStream(service), this); + + byte[] newLineBytes = NEW_LINE.getBytes(charset); + bodyWriter.write(newLineBytes); + uploadedBytes += newLineBytes.length; + } + } + + @Override + protected void onSuccessfulUpload() { + addAllFilesToSuccessfullyUploadedFiles(); + } + +} + diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index 61c39969..289f8675 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -2,21 +2,34 @@ package gr.thmmy.mthmmy.base; import android.Manifest; import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.preference.PreferenceManager; + import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.firebase.messaging.FirebaseMessaging; import com.mikepenz.fontawesome_typeface_library.FontAwesome; @@ -28,6 +41,9 @@ import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.ProfileDrawerItem; +import com.snatik.storage.Storage; + +import net.gotev.uploadservice.UploadService; import java.io.BufferedReader; import java.io.File; @@ -35,14 +51,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.lifecycle.ViewModelProviders; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.AboutActivity; import gr.thmmy.mthmmy.activities.LoginActivity; @@ -53,12 +61,15 @@ import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxActivity; +import gr.thmmy.mthmmy.activities.upload.UploadActivity; import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.services.DownloadHelper; +import gr.thmmy.mthmmy.services.UploadsReceiver; import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.utils.FileUtils; import gr.thmmy.mthmmy.viewmodel.BaseViewModel; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import okhttp3.OkHttpClient; import ru.noties.markwon.LinkResolverDef; import ru.noties.markwon.Markwon; @@ -73,6 +84,7 @@ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB; import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; +import static gr.thmmy.mthmmy.services.UploadsReceiver.UPLOAD_ID_KEY; import static gr.thmmy.mthmmy.session.SessionManager.SUCCESS; import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType; @@ -83,6 +95,9 @@ public abstract class BaseActivity extends AppCompatActivity { //SessionManager protected static SessionManager sessionManager; + //Storage manager + protected Storage storage; + //Bookmarks public static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs"; public static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey"; @@ -97,6 +112,9 @@ public abstract class BaseActivity extends AppCompatActivity { //Common UI elements protected Toolbar toolbar; protected Drawer drawer; + //Uploads progress dialog + UploadsShowDialogReceiver uploadsShowDialogReceiver; + AlertDialog uploadsProgressDialog; private MainActivity mainActivity; private boolean isMainActivity; @@ -124,16 +142,25 @@ public abstract class BaseActivity extends AppCompatActivity { BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class); baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> setTopicBookmark(thisPageBookmarkMenuButton)); + + storage = new Storage(getApplicationContext()); } @Override protected void onResume() { super.onResume(); updateDrawer(); - if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown){ - isUserConsentDialogShown=true; + if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown) { + isUserConsentDialogShown = true; showUserConsentDialog(); } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (uploadsShowDialogReceiver == null) { + uploadsShowDialogReceiver = new UploadsShowDialogReceiver(this); + } + this.registerReceiver(uploadsShowDialogReceiver, new IntentFilter(UploadsReceiver.ACTION_COMBINED_UPLOAD)); + } } @Override @@ -141,6 +168,10 @@ public abstract class BaseActivity extends AppCompatActivity { super.onPause(); if (drawer != null) //close drawer animation after returning to activity drawer.closeDrawer(); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && uploadsShowDialogReceiver != null) { + this.unregisterReceiver(uploadsShowDialogReceiver); + } } @@ -152,6 +183,10 @@ public abstract class BaseActivity extends AppCompatActivity { return sessionManager; } + public Storage getStorage() { + return storage; + } + //TODO: move stuff below (?) //------------------------------------------DRAWER STUFF---------------------------------------- protected static final int HOME_ID = 0; @@ -257,14 +292,15 @@ public abstract class BaseActivity extends AppCompatActivity { .withName(R.string.downloads) .withIcon(downloadsIcon) .withSelectedIcon(downloadsIconSelected); -// uploadItem = new PrimaryDrawerItem() -// .withTextColor(primaryColor) -// .withSelectedColor(selectedPrimaryColor) -// .withSelectedTextColor(selectedSecondaryColor) -// .withIdentifier(UPLOAD_ID) -// .withName(R.string.upload) -// .withIcon(uploadIcon) -// .withSelectedIcon(uploadIconSelected); + + uploadItem = new PrimaryDrawerItem() + .withTextColor(primaryColor) + .withSelectedColor(selectedPrimaryColor) + .withSelectedTextColor(selectedSecondaryColor) + .withIdentifier(UPLOAD_ID) + .withName(R.string.upload) + .withIcon(uploadIcon) + .withSelectedIcon(uploadIconSelected); shoutboxItem = new PrimaryDrawerItem() .withTextColor(primaryColor) @@ -397,11 +433,11 @@ public abstract class BaseActivity extends AppCompatActivity { intent.putExtras(extras); startActivity(intent); } -// } else if (drawerItem.equals(UPLOAD_ID)) { -// if (!(BaseActivity.this instanceof UploadActivity)) { -// Intent intent = new Intent(BaseActivity.this, UploadActivity.class); -// startActivity(intent); -// } + } else if (drawerItem.equals(UPLOAD_ID)) { + if (!(BaseActivity.this instanceof UploadActivity)) { + Intent intent = new Intent(BaseActivity.this, UploadActivity.class); + startActivity(intent); + } } else if (drawerItem.equals(BOOKMARKS_ID)) { if (!(BaseActivity.this instanceof BookmarksActivity)) { Intent intent = new Intent(BaseActivity.this, BookmarksActivity.class); @@ -452,7 +488,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (!sessionManager.isLoggedIn()) //When logged out or if user is guest { drawer.removeItem(DOWNLOADS_ID); -// drawer.removeItem(UPLOAD_ID); + drawer.removeItem(UPLOAD_ID); loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login profileDrawerItem.withName(sessionManager.getUsername()); setDefaultAvatar(); @@ -460,9 +496,9 @@ public abstract class BaseActivity extends AppCompatActivity { if (!drawer.getDrawerItems().contains(downloadsItem)) { drawer.addItemAtPosition(downloadsItem, 4); } -// if (!drawer.getDrawerItems().contains(uploadItem)) { -// drawer.addItemAtPosition(uploadItem, 5); -// } + if (!drawer.getDrawerItems().contains(uploadItem)) { + drawer.addItemAtPosition(uploadItem, 5); + } loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout profileDrawerItem.withName(sessionManager.getUsername()); if (sessionManager.hasAvatar()) @@ -687,10 +723,10 @@ public abstract class BaseActivity extends AppCompatActivity { //-------------------------------------------BOOKMARKS END------------------------------------------ //-------PERMS--------- - private static final int PERMISSIONS_REQUEST_CODE = 69; + private static final int DOWNLOAD_REQUEST_CODE = 69; //Arbitrary, application specific //True if permissions are OK - private boolean checkPerms() { + protected boolean checkPerms() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { String[] PERMISSIONS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, @@ -703,13 +739,13 @@ public abstract class BaseActivity extends AppCompatActivity { } //Display popup for user to grant permission - private void requestPerms() { //Runtime permissions request for devices with API >= 23 + protected void requestPerms(int code) { //Runtime permissions request for devices with API >= 23 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { String[] PERMISSIONS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; - requestPermissions(PERMISSIONS_STORAGE, PERMISSIONS_REQUEST_CODE); + requestPermissions(PERMISSIONS_STORAGE, code); } } @@ -718,8 +754,9 @@ public abstract class BaseActivity extends AppCompatActivity { public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions , @NonNull int[] grantResults) { switch (permsRequestCode) { - case PERMISSIONS_REQUEST_CODE: - downloadFile(); + case DOWNLOAD_REQUEST_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + prepareDownload(tempThmmyFile); break; } } @@ -733,7 +770,7 @@ public abstract class BaseActivity extends AppCompatActivity { prepareDownload(thmmyFile); else { tempThmmyFile = thmmyFile; - requestPerms(); + requestPerms(DOWNLOAD_REQUEST_CODE); } } @@ -855,6 +892,93 @@ public abstract class BaseActivity extends AppCompatActivity { editor.putBoolean(getString(R.string.pref_privacy_analytics_enable_key), enabled).apply(); } + //------------------------------------------ UPLOADS ------------------------------------------- + private class UploadsShowDialogReceiver extends BroadcastReceiver { + private final Context activityContext; + + UploadsShowDialogReceiver(Context activityContext) { + this.activityContext = activityContext; + } + + @Override + public void onReceive(Context context, Intent intent) { + Bundle intentBundle = intent.getExtras(); + if (intentBundle == null) { + return; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + String dialogUploadID = intentBundle.getString(UPLOAD_ID_KEY); + + /*String retryFilename = intentBundle.getString(UPLOAD_RETRY_FILENAME); + String retryCategory = intentBundle.getString(UPLOAD_RETRY_CATEGORY); + String retryTitleText = intentBundle.getString(UPLOAD_RETRY_TITLE); + String retryDescription = intentBundle.getString(UPLOAD_RETRY_DESCRIPTION); + String retryIcon = intentBundle.getString(UPLOAD_RETRY_ICON); + String retryUploaderProfile = intentBundle.getString(UPLOAD_RETRY_UPLOADER); + Uri retryFileUri = (Uri) intentBundle.get(UPLOAD_RETRY_FILE_URI); + + Intent retryIntent = new Intent(context, UploadsReceiver.class); + retryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + retryIntent.setAction(UploadsReceiver.ACTION_RETRY_UPLOAD); + + retryIntent.putExtra(UPLOAD_RETRY_FILENAME, retryFilename); + retryIntent.putExtra(UPLOAD_RETRY_CATEGORY, retryCategory); + retryIntent.putExtra(UPLOAD_RETRY_TITLE, retryTitleText); + retryIntent.putExtra(UPLOAD_RETRY_DESCRIPTION, retryDescription); + retryIntent.putExtra(UPLOAD_RETRY_ICON, retryIcon); + retryIntent.putExtra(UPLOAD_RETRY_UPLOADER, retryUploaderProfile); + retryIntent.putExtra(UPLOAD_RETRY_FILE_URI, retryFileUri);*/ + + if (uploadsProgressDialog == null) { + AlertDialog.Builder progressDialogBuilder = new AlertDialog.Builder(activityContext); + LayoutInflater inflater = LayoutInflater.from(activityContext); + LinearLayout progressDialogLayout = (LinearLayout) inflater.inflate(R.layout.dialog_upload_progress, null); + + MaterialProgressBar dialogProgressBar = progressDialogLayout.findViewById(R.id.dialogProgressBar); + dialogProgressBar.setMax(100); + + progressDialogBuilder.setView(progressDialogLayout); + + uploadsProgressDialog = progressDialogBuilder.create(); + if (!UploadService.getTaskList().contains(dialogUploadID)) { + //Upload probably failed at this point + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "", (progressDialog, progressWhich) -> { + /*LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context.getApplicationContext()); + localBroadcastManager.sendBroadcast(multipartUploadRetryIntent);*/ + //uploadsProgressDialog.dismiss(); + + //context.sendBroadcast(retryIntent); + }); + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), (progressDialog, progressWhich) -> { + uploadsProgressDialog.dismiss(); + }); + + TextView dialogProgressText = progressDialogLayout.findViewById(R.id.dialog_upload_progress_text); + dialogProgressBar.setVisibility(View.GONE); + dialogProgressText.setText(getString(R.string.upload_failed)); + + uploadsProgressDialog.show(); + } else { + //Empty buttons are needed, they are updated with correct values in the receiver + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "placeholder", (progressDialog, progressWhich) -> { + }); + uploadsProgressDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "placeholder", (progressDialog, progressWhich) -> { + }); + + UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, null); + //UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, retryIntent); + uploadsProgressDialog.show(); + } + } else { + UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, null); + //UploadsReceiver.setDialogDisplay(uploadsProgressDialog, dialogUploadID, retryIntent); + uploadsProgressDialog.show(); + } + } + } + } + //----------------------------------MISC---------------------- protected void setMainActivity(MainActivity mainActivity) { this.mainActivity = mainActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index ce4e4068..ba0e6a0f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java @@ -10,11 +10,15 @@ import android.os.Bundle; import android.util.DisplayMetrics; import android.widget.ImageView; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; + import com.crashlytics.android.Crashlytics; import com.crashlytics.android.core.CrashlyticsCore; import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.cache.SetCookieCache; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; +import com.google.firebase.FirebaseApp; import com.google.firebase.analytics.FirebaseAnalytics; import com.itkacher.okhttpprofiler.OkHttpProfilerInterceptor; import com.jakewharton.picasso.OkHttp3Downloader; @@ -33,8 +37,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.session.SessionManager; @@ -50,7 +52,8 @@ import timber.log.Timber; public class BaseApplication extends Application { private static BaseApplication baseApplication; //BaseApplication singleton - //Firebase Analytics + //Firebase + private static String firebaseProjectId; private FirebaseAnalytics firebaseAnalytics; //Client & SessionManager @@ -62,7 +65,6 @@ public class BaseApplication extends Application { //Display Metrics private static float dpWidth; - public static BaseApplication getInstance() { return baseApplication; } @@ -86,6 +88,7 @@ public class BaseApplication extends Application { else Timber.i("Starting app with Crashlytics disabled."); + firebaseProjectId = FirebaseApp.getInstance().getOptions().getProjectId(); firebaseAnalytics = FirebaseAnalytics.getInstance(this); boolean enableAnalytics = settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_analytics_enable_key), false); firebaseAnalytics.setAnalyticsCollectionEnabled(enableAnalytics); @@ -215,4 +218,8 @@ public class BaseApplication extends Application { } else Timber.i("Crashlytics were already initialized for this app session."); } + + public static String getFirebaseProjectId(){ + return firebaseProjectId; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java index 03622e83..c2c1a578 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; + import okhttp3.OkHttpClient; public abstract class BaseFragment extends Fragment { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java index 3b41d07c..97f7d42f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java @@ -26,16 +26,17 @@ import android.widget.PopupWindow; import android.widget.ScrollView; import android.widget.TextView; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; - -import java.util.Objects; - import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatImageButton; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.Objects; + import gr.thmmy.mthmmy.R; import timber.log.Timber; diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java index cd57fb5e..903ff5fe 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java @@ -9,11 +9,12 @@ import android.view.MotionEvent; import android.view.inputmethod.InputConnection; import android.widget.LinearLayout; -import java.util.HashSet; - import androidx.appcompat.widget.AppCompatImageButton; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import java.util.HashSet; + import gr.thmmy.mthmmy.R; public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java index c3b0758b..efcb9519 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.widget.AppCompatImageButton; import androidx.recyclerview.widget.RecyclerView; + import gr.thmmy.mthmmy.R; public class EmojiKeyboardAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java index 98277550..732149ae 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java @@ -7,6 +7,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.widget.AppCompatImageButton; import androidx.recyclerview.widget.RecyclerView; + import gr.thmmy.mthmmy.R; public class FormatButtonsAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java b/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java index ac622a64..1da43458 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java @@ -1,11 +1,11 @@ package gr.thmmy.mthmmy.model; -import java.util.ArrayList; -import java.util.Objects; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.Objects; + public class Bookmark implements java.io.Serializable { private final String title, id; private boolean isNotificationsEnabled; diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java index 0c66ba83..7e0b7f55 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java @@ -1,10 +1,10 @@ package gr.thmmy.mthmmy.model; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.Objects; -import androidx.annotation.Nullable; - /** * Class that defines a topic's post. All member variables are declared final (thus no setters are * supplied). Class has two constructors and getter methods for all variables. diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java b/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java new file mode 100644 index 00000000..fb5c2161 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/model/UploadFile.java @@ -0,0 +1,45 @@ +package gr.thmmy.mthmmy.model; + +import android.net.Uri; + +import androidx.annotation.Nullable; + +import java.io.File; + +public class UploadFile { + private final boolean isCameraPhoto; + private Uri fileUri; + private File photoFile; + + private UploadFile() { + isCameraPhoto = false; + fileUri = null; + photoFile = null; + } + + public UploadFile(boolean isCameraPhoto, Uri fileUri, @Nullable File photoFile) { + this.isCameraPhoto = isCameraPhoto; + this.fileUri = fileUri; + this.photoFile = photoFile; + } + + public boolean isCameraPhoto() { + return isCameraPhoto; + } + + public Uri getFileUri() { + return fileUri; + } + + public File getPhotoFile() { + return photoFile; + } + + public void setFileUri(Uri fileUri) { + this.fileUri = fileUri; + } + + public void setPhotoFile(File photoFile) { + this.photoFile = photoFile; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java b/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java index 5631b325..79c36d4b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java +++ b/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java @@ -13,6 +13,10 @@ import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.preference.PreferenceManager; + import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; @@ -23,9 +27,6 @@ import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; -import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.base.BaseApplication; @@ -260,10 +261,9 @@ public class NotificationService extends FirebaseMessagingService { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // Since Android Oreo notification channel is needed. - if (buildVersion >= Build.VERSION_CODES.O){ - if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) + if (buildVersion >= Build.VERSION_CODES.O && notificationManager.getNotificationChannel(CHANNEL_ID) == null) notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)); - } + notificationManager.notify(NEW_POST_TAG, notificationId, notificationBuilder.build()); diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java b/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java new file mode 100644 index 00000000..1406eb57 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/services/UploadsReceiver.java @@ -0,0 +1,233 @@ +package gr.thmmy.mthmmy.services; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; + +import com.snatik.storage.Storage; + +import net.gotev.uploadservice.ServerResponse; +import net.gotev.uploadservice.UploadInfo; +import net.gotev.uploadservice.UploadService; +import net.gotev.uploadservice.UploadServiceBroadcastReceiver; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.upload.UploadsHelper; +import gr.thmmy.mthmmy.activities.upload.multipart.MultipartUploadException; +import gr.thmmy.mthmmy.base.BaseApplication; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; +import timber.log.Timber; + +public class UploadsReceiver extends UploadServiceBroadcastReceiver { + public static final String UPLOAD_ID_KEY = "UPLOAD_ID_KEY"; + + public static final String ACTION_COMBINED_UPLOAD = "ACTION_COMBINED_UPLOAD"; + public static final String ACTION_CANCEL_UPLOAD = "ACTION_CANCEL_UPLOAD"; + public static final String ACTION_RETRY_UPLOAD = "ACTION_RETRY_UPLOAD"; + + /*public static final String UPLOAD_RETRY_FILENAME = "UPLOAD_RETRY_FILENAME"; + public static final String UPLOAD_RETRY_CATEGORY = "UPLOAD_RETRY_CATEGORY"; + public static final String UPLOAD_RETRY_TITLE = "UPLOAD_RETRY_TITLE"; + public static final String UPLOAD_RETRY_DESCRIPTION = "UPLOAD_RETRY_DESCRIPTION"; + public static final String UPLOAD_RETRY_ICON = "UPLOAD_RETRY_ICON"; + public static final String UPLOAD_RETRY_UPLOADER = "UPLOAD_RETRY_UPLOADER"; + public static final String UPLOAD_RETRY_FILE_URI = "UPLOAD_RETRY_FILE_URI";*/ + + private Storage storage; + private static AlertDialog uploadProgressDialog; + private static String dialogUploadID; + //private static Intent multipartUploadRetryIntent; + + @Override + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + Bundle intentBundle = intent.getExtras(); + if (intentAction == null || intentBundle == null) { + super.onReceive(context, intent); + return; + } + + switch (intentAction) { + case ACTION_CANCEL_UPLOAD: + String uploadID = intentBundle.getString(UPLOAD_ID_KEY); + Timber.d("Received ACTION_CANCEL_UPLOAD (id: %s)", uploadID); + UploadService.stopUpload(uploadID); + break; + /*case ACTION_RETRY_UPLOAD: + String retryFilename = intentBundle.getString(UPLOAD_RETRY_FILENAME); + String retryCategory = intentBundle.getString(UPLOAD_RETRY_CATEGORY); + String retryTitleText = intentBundle.getString(UPLOAD_RETRY_TITLE); + String retryDescription = intentBundle.getString(UPLOAD_RETRY_DESCRIPTION); + String retryIcon = intentBundle.getString(UPLOAD_RETRY_ICON); + String retryUploaderProfile = intentBundle.getString(UPLOAD_RETRY_UPLOADER); + Uri retryFileUri = (Uri) intentBundle.get(UPLOAD_RETRY_FILE_URI); + String retryUploadID = UUID.randomUUID().toString(); + + UploadActivity.uploadFile(context, retryUploadID, + UploadActivity.getConfigForUpload(context, retryUploadID, retryFilename, retryCategory, + retryTitleText, retryDescription, retryIcon, retryUploaderProfile, retryFileUri), + retryCategory, retryTitleText, retryDescription, retryIcon, + retryUploaderProfile, retryFileUri); + + break;*/ + default: + super.onReceive(context, intent); + break; + } + } + + @Override + public void onProgress(Context context, UploadInfo uploadInfo) { + Timber.i("Upload in progress (id: %s)",uploadInfo.getUploadId()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && + uploadInfo.getUploadId().equals(dialogUploadID) && + uploadProgressDialog != null) { + Button alertDialogNeutral = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + alertDialogNeutral.setText(R.string.upload_resume_in_background); + alertDialogNeutral.setOnClickListener(v -> uploadProgressDialog.dismiss()); + + Button alertDialogNegative = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEGATIVE); + alertDialogNegative.setText(R.string.cancel); + alertDialogNegative.setOnClickListener(v -> { + Timber.d("Cancelling upload (id: %s)", dialogUploadID); + UploadService.stopUpload(dialogUploadID); + uploadProgressDialog.dismiss(); + }); + + if (uploadProgressDialog.isShowing()) { + Window progressWindow = uploadProgressDialog.getWindow(); + if (progressWindow != null) { + MaterialProgressBar dialogProgressBar = progressWindow.findViewById(R.id.dialogProgressBar); + TextView dialogProgressText = progressWindow.findViewById(R.id.dialog_upload_progress_text); + + dialogProgressBar.setProgress(uploadInfo.getProgressPercent()); + dialogProgressText.setText(context.getResources().getString( + R.string.upload_progress_dialog_bytes_uploaded, + (float) uploadInfo.getUploadRate(), + (int) uploadInfo.getUploadedBytes() / 1000, + (int) uploadInfo.getTotalBytes() / 1000)); + } + + if (uploadInfo.getUploadedBytes() == uploadInfo.getTotalBytes()) { + uploadProgressDialog.dismiss(); + } + } + } + } + + @Override + public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, + Exception exception) { + Timber.i("Error while uploading (id: %s)",uploadInfo.getUploadId()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && + uploadInfo.getUploadId().equals(dialogUploadID) && + uploadProgressDialog != null) { + /*Button alertDialogNeutral = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + alertDialogNeutral.setText("Retry"); + alertDialogNeutral.setOnClickListener(v -> { + if (multipartUploadRetryIntent != null) { + context.sendBroadcast(multipartUploadRetryIntent); + } + uploadProgressDialog.dismiss(); + });*/ + + Button alertDialogNegative = uploadProgressDialog.getButton(AlertDialog.BUTTON_NEGATIVE); + alertDialogNegative.setText(R.string.cancel); + alertDialogNegative.setOnClickListener(v -> { + cancelNotification(context, uploadInfo.getNotificationID()); + UploadsHelper.deleteTempFiles(storage); + uploadProgressDialog.dismiss(); + }); + + if (uploadProgressDialog.isShowing()) { + Window progressWindow = uploadProgressDialog.getWindow(); + if (progressWindow != null) { + MaterialProgressBar dialogProgressBar = progressWindow.findViewById(R.id.dialogProgressBar); + TextView dialogProgressText = progressWindow.findViewById(R.id.dialog_upload_progress_text); + + dialogProgressBar.setVisibility(View.GONE); + dialogProgressText.setText(R.string.upload_failed); + } + + if (uploadInfo.getUploadedBytes() == uploadInfo.getTotalBytes()) { + uploadProgressDialog.dismiss(); + } + } + } else { + cancelNotification(context, uploadInfo.getNotificationID()); + Intent combinedActionsIntent = new Intent(UploadsReceiver.ACTION_COMBINED_UPLOAD); + combinedActionsIntent.putExtra(UploadsReceiver.UPLOAD_ID_KEY, uploadInfo.getUploadId()); + context.sendBroadcast(combinedActionsIntent); + } + + Toast.makeText(context.getApplicationContext(), R.string.upload_failed, Toast.LENGTH_SHORT).show(); + if (storage == null) { + storage = new Storage(context.getApplicationContext()); + } + } + + @Override + public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadProgressDialog = null; + dialogUploadID = null; + } + + String response = serverResponse.getBodyAsString(); + if(response.contains("Η προσθήκη του αρχείου ήταν επιτυχημένη.")||response.contains("The upload was successful.")){ + Timber.i("Upload completed successfully (id: %s)",uploadInfo.getUploadId()); + Toast.makeText(context.getApplicationContext(), "Upload completed successfully", Toast.LENGTH_SHORT).show(); + BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_upload", null); + } + else { + MultipartUploadException multipartUploadException = new MultipartUploadException(response); + Timber.e(multipartUploadException); + onError(context,uploadInfo,serverResponse,multipartUploadException); + } + + if (storage == null) { + storage = new Storage(context.getApplicationContext()); + } + + UploadsHelper.deleteTempFiles(storage); + } + + @Override + public void onCancelled(Context context, UploadInfo uploadInfo) { + Timber.i("Upload cancelled (id: %s)", uploadInfo.getUploadId()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + uploadProgressDialog = null; + dialogUploadID = null; + } + + Toast.makeText(context.getApplicationContext(), R.string.upload_cancelled, Toast.LENGTH_SHORT).show(); + if (storage == null) + storage = new Storage(context.getApplicationContext()); + + //cancelNotification(context, uploadInfo.getNotificationID()); + UploadsHelper.deleteTempFiles(storage); + } + + public static void setDialogDisplay(AlertDialog uploadProgressDialog, String dialogUploadID, + Intent multipartUploadRetryIntent) { + UploadsReceiver.uploadProgressDialog = uploadProgressDialog; + UploadsReceiver.dialogUploadID = dialogUploadID; + //UploadsReceiver.multipartUploadRetryIntent = multipartUploadRetryIntent; + } + + private void cancelNotification(Context context, int notificationId){ + NotificationManager notificationManager = (NotificationManager) context.getApplicationContext(). + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) + notificationManager.cancel(notificationId); + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java index 42f02375..4e91bc72 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java +++ b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java @@ -2,6 +2,9 @@ package gr.thmmy.mthmmy.session; import android.content.SharedPreferences; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; @@ -14,8 +17,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import gr.thmmy.mthmmy.utils.parsing.ParseException; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import okhttp3.Cookie; diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java b/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java index b64bb57d..77293dfe 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java @@ -10,12 +10,12 @@ import android.widget.AdapterView; import android.widget.SpinnerAdapter; import android.widget.TextView; +import androidx.appcompat.widget.AppCompatSpinner; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import androidx.appcompat.widget.AppCompatSpinner; - public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner { public AppCompatSpinnerWithoutDefault(Context context) { super(context); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java index b9907e93..67ba0395 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java @@ -17,8 +17,8 @@ public class CrashReporter { public static void reportForumInfo(Document document) { ParseHelpers.Theme theme = ParseHelpers.parseTheme(document); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); - String themeKey = "forum theme", themeValue = null; - String languageKey = "forum language", languageValue = null; + String themeKey = "forum theme", themeValue; + String languageKey = "forum language", languageValue; switch (theme) { case SCRIBBLES2: themeValue = "Scribbles2"; @@ -32,19 +32,17 @@ public class CrashReporter { case HELIOS_MULTI: themeValue = "Helios_Multi"; break; - case THEME_UNKNOWN: + default: themeValue = "Unknown theme"; - break; - } - switch (language) { - case GREEK: - languageValue = "Greek"; - break; - case ENGLISH: - languageValue = "English"; - break; } + if (language == ParseHelpers.Language.GREEK) + languageValue = "Greek"; + else if (language == ParseHelpers.Language.ENGLISH) + languageValue = "English"; + else + languageValue = "Unknown"; + Crashlytics.setString(themeKey, themeValue); Crashlytics.setString(languageKey, languageValue); Crashlytics.setBool("isLoggedIn", BaseApplication.getInstance().getSessionManager().isLoggedIn()); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java index c4674134..8f85459a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java @@ -27,8 +27,7 @@ public class CrashReportingTree extends DebugTree { Crashlytics.log(level + "/" + tag + ": " + message); - if(priority == Log.ERROR) - { + if(priority == Log.ERROR) { if (t!=null) Crashlytics.logException(t); else diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java index de9abfd0..f4dd5042 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java @@ -4,6 +4,7 @@ import android.content.Context; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import timber.log.Timber; public class CustomLinearLayoutManager extends LinearLayoutManager { diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java index 1c8eb0b0..e1ef6f9e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java @@ -1,10 +1,17 @@ package gr.thmmy.mthmmy.utils; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.io.File; -import androidx.annotation.NonNull; +import gr.thmmy.mthmmy.R; import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; @@ -21,7 +28,94 @@ public class FileUtils { return type; } - public static boolean fileNameExists (String fileName) { + public static boolean fileNameExists(String fileName) { return fileName != null && (new File(SAVE_DIR.getAbsolutePath(), fileName)).isFile(); } + + @Nullable + public static String getFileExtension(@NonNull String filename) { + String fileExtension; + + if (!filename.contains(".")) { + return null; + } + if (filename.toLowerCase().endsWith(".tar.gz")) { + fileExtension = filename.substring(filename.length() - 7); + } else { + fileExtension = filename.substring(filename.lastIndexOf(".")); + } + + return fileExtension; + } + + public static String getFilenameWithoutExtension(String filename) { + String fileExtension = getFileExtension(filename); + + return fileExtension == null + ? filename + : filename.substring(0, filename.indexOf(fileExtension)); + } + + @NonNull + public static String filenameFromUri(Context context, Uri uri) { + String filename = null; + if (uri.getScheme().equals("content")) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + } + if (filename == null) { + filename = uri.getPath(); + int cut = filename.lastIndexOf('/'); + if (cut != -1) { + filename = filename.substring(cut + 1); + } + } + + return filename; + } + + public static long sizeFromUri(Context context, @NonNull Uri uri) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + } + } + return -1; + } + + /** + * Returns a String with a single FontAwesome typeface character corresponding to this file's + * extension. + * + * @param filename String with filename containing file's extension + * @return FontAwesome character according to file's type + * @see FontAwesome + */ + @NonNull + public static String faIconFromFilename(Context context, String filename) { + filename = filename.toLowerCase(); + + if (filename.contains("jpg") || filename.contains("gif") || filename.contains("jpeg") + || filename.contains("png")) + return context.getResources().getString(R.string.fa_file_image_o); + else if (filename.contains("pdf")) + return context.getResources().getString(R.string.fa_file_pdf_o); + else if (filename.contains("zip") || filename.contains("rar") || filename.contains("tar.gz")) + return context.getResources().getString(R.string.fa_file_zip_o); + else if (filename.contains("txt")) + return context.getResources().getString(R.string.fa_file_text_o); + else if (filename.contains("doc") || filename.contains("docx")) + return context.getResources().getString(R.string.fa_file_word_o); + else if (filename.contains("xls") || filename.contains("xlsx")) + return context.getResources().getString(R.string.fa_file_excel_o); + else if (filename.contains("pps")) + return context.getResources().getString(R.string.fa_file_powerpoint_o); + else if (filename.contains("mpg")) + return context.getResources().getString(R.string.fa_file_video_o); + + return context.getResources().getString(R.string.fa_file); + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java index a45e1a3f..a77502f3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java @@ -4,13 +4,13 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; - import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + /** * Extends FloatingActionButton's behavior so the button will hide when scrolling down and show * otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown. @@ -47,7 +47,13 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.BehaviorWhen a nested ScrollView is scrolled down, the view will disappear. diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/TakePhoto.java b/app/src/main/java/gr/thmmy/mthmmy/utils/TakePhoto.java new file mode 100644 index 00000000..c5b4c490 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/TakePhoto.java @@ -0,0 +1,173 @@ +package gr.thmmy.mthmmy.utils; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.AssetFileDescriptor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.FileProvider; +import androidx.exifinterface.media.ExifInterface; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import timber.log.Timber; + +public class TakePhoto { + private static final int DEFAULT_MIN_WIDTH_QUALITY = 400; + private static final String IMAGE_CONTENT_DESCRIPTION = "mTHMMY uploads image"; + + @Nullable + public static Intent getIntent(Context context, @NonNull File photoFile) { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + //Ensures that there's a camera activity to handle the intent + if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) { + Uri photoURI = FileProvider.getUriForFile(context, context.getPackageName() + + ".provider", photoFile); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + + //Grants necessary permissions for Gallery to use the Uri + List resInfoList = context.getPackageManager(). + queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, photoURI, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION | + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return takePictureIntent; + } + return null; + } + + public static Uri processResult(Context context, File photoFile) { + Bitmap bitmap; + Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", photoFile); + + bitmap = getImageResized(context, fileUri); + int rotation = getRotation(context, fileUri); + bitmap = rotate(bitmap, rotation); + + try { + FileOutputStream out = new FileOutputStream(photoFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + out.flush(); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return fileUri; + } + + @Nullable + public static File createImageFile(Context context) { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); + String imageFileName = "mTHMMY_" + timeStamp + ".jpg"; + + File imageFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + + File.separator + "mTHMMY"); + + if (!imageFolder.exists()&&!imageFolder.mkdirs()) { + Timber.w("Photos folder build returned false in %s", TakePhoto.class.getSimpleName()); + Toast.makeText(context, "Couldn't create photos directory", Toast.LENGTH_SHORT).show(); + return null; + } + + return new File(imageFolder, imageFileName); + } + + public static void galleryAddPic(Context context, File photoFile) { + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.TITLE, photoFile.getName()); + values.put(MediaStore.Images.Media.DESCRIPTION, IMAGE_CONTENT_DESCRIPTION); + values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); + values.put(MediaStore.Images.ImageColumns.BUCKET_ID, photoFile.toString().toLowerCase(Locale.US).hashCode()); + values.put(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, photoFile.getName().toLowerCase(Locale.US)); + values.put("_data", photoFile.getAbsolutePath()); + + ContentResolver cr = context.getContentResolver(); + cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } + + private static Bitmap getImageResized(Context context, Uri selectedImage) { + Bitmap bm; + int[] sampleSizes = new int[]{5, 3, 2, 1}; + int i = 0; + do { + bm = decodeBitmap(context, selectedImage, sampleSizes[i]); + i++; + } while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length); + return bm; + } + + private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = sampleSize; + + AssetFileDescriptor fileDescriptor = null; + try { + fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + assert fileDescriptor != null; + return BitmapFactory.decodeFileDescriptor( + fileDescriptor.getFileDescriptor(), null, options); + } + + private static int getRotation(Context context, Uri imageUri) { + int rotation = 0; + try { + + context.getContentResolver().notifyChange(imageUri, null); + ExifInterface exif = new ExifInterface(imageUri.getPath()); + int orientation = exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_270: + rotation = 270; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + rotation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_90: + rotation = 90; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + return rotation; + } + + private static Bitmap rotate(Bitmap bm, int rotation) { + if (rotation != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + } + return bm; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ToggledBackgroundButton.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ToggledBackgroundButton.java new file mode 100644 index 00000000..6c189e87 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ToggledBackgroundButton.java @@ -0,0 +1,27 @@ +package gr.thmmy.mthmmy.utils; +import android.content.Context; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatButton; + +public class ToggledBackgroundButton extends AppCompatButton { + + public ToggledBackgroundButton(Context context) { + super(context); + } + + public ToggledBackgroundButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ToggledBackgroundButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setEnabled(boolean enabled) { + setAlpha(enabled ? 1 : 0.5f); + super.setEnabled(enabled); + } +} + diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java index 942e789b..5fc06664 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java @@ -1,13 +1,12 @@ package gr.thmmy.mthmmy.utils.parsing; /** - * ParseException is to be used for errors while parsing. + * Use ParseException for errors while parsing. */ public class ParseException extends RuntimeException { public ParseException() {} - public ParseException(String message) - { + public ParseException(String message) { super(message); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java index f8a3767f..e9792ac0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java @@ -9,6 +9,7 @@ import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import java.util.ArrayList; +import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,7 +60,7 @@ public class ParseHelpers { /** * An enum describing a forum page's language by defining the types:
    *
  • {@link #PAGE_INCOMPLETE}
  • - *
  • {@link #UNDEFINED_LANGUAGE}
  • + *
  • {@link #UNKNOWN_LANGUAGE}
  • *
  • {@link #ENGLISH}
  • *
  • {@link #GREEK}
  • *
@@ -78,9 +79,9 @@ public class ParseHelpers { */ PAGE_INCOMPLETE, /** - * Page language is not (yet) supported. + * Page language could not be determined/ not (yet) supported. */ - UNDEFINED_LANGUAGE; + UNKNOWN_LANGUAGE; /** * Returns one of the supported forum languages. @@ -93,13 +94,12 @@ public class ParseHelpers { Element welcoming = page.select("h3").first(); if (welcoming == null) { Element welcomingGuest = page.select("div[id=myuser]").first(); - if (welcomingGuest != null) { - if (welcomingGuest.text().contains("Welcome")) return ENGLISH; - } + if (welcomingGuest != null && welcomingGuest.text().contains("Welcome")) + return ENGLISH; return PAGE_INCOMPLETE; } else if (welcoming.text().contains("Καλώς ορίσατε")) return GREEK; else if (welcoming.text().contains("Hey")) return ENGLISH; - else return UNDEFINED_LANGUAGE; + else return UNKNOWN_LANGUAGE; } } @@ -113,16 +113,17 @@ public class ParseHelpers { public static Theme parseTheme(Document page) { Element stylesheet = page.select("link[rel=stylesheet]").first(); - if (stylesheet.attr("href").contains("scribbles2")) - return Theme.SCRIBBLES2; - else if (stylesheet.attr("href").contains("helios_multi")) - return Theme.HELIOS_MULTI; - else if (stylesheet.attr("href").contains("smfone")) - return Theme.SMFONE_BLUE; - else if (stylesheet.attr("href").contains("default")) - return Theme.SMF_DEFAULT; - else - return Theme.THEME_UNKNOWN; + if(stylesheet!=null){ + if (stylesheet.attr("href").contains("scribbles2")) + return Theme.SCRIBBLES2; + else if (stylesheet.attr("href").contains("helios_multi")) + return Theme.HELIOS_MULTI; + else if (stylesheet.attr("href").contains("smfone")) + return Theme.SMFONE_BLUE; + else if (stylesheet.attr("href").contains("default")) + return Theme.SMF_DEFAULT; + } + return Theme.THEME_UNKNOWN; } /** @@ -307,7 +308,7 @@ public class ParseHelpers { * @param html html to parse * @return a document with deobfuscated emails */ - public static Document parse(String html){ + public static Document parse(String html) { Document document = Jsoup.parse(html); deobfuscateElements(document.select("span.__cf_email__,a.__cf_email__"), true); return document; @@ -316,19 +317,18 @@ public class ParseHelpers { /** * Use this method instead of parse() if you are targeting specific elements */ - public static void deobfuscateElements(Elements elements, boolean found){ - if(!found) + public static void deobfuscateElements(Elements elements, boolean found) { + if (!found) elements = elements.select("span.__cf_email__,a.__cf_email__"); for (Element obfuscatedElement : elements) { String deobfuscatedEmail = deobfuscateEmail(obfuscatedElement.attr("data-cfemail")); - if(obfuscatedElement.is("span")){ + if (obfuscatedElement.is("span")) { Element parent = obfuscatedElement.parent(); - if (parent.is("a")&&parent.attr("href").contains("email-protection")) - parent.attr("href", "mailto:"+deobfuscatedEmail); - } - else if (obfuscatedElement.attr("href").contains("email-protection")) - obfuscatedElement.attr("href", "mailto:"+deobfuscatedEmail); + if (parent.is("a") && parent.attr("href").contains("email-protection")) + parent.attr("href", "mailto:" + deobfuscatedEmail); + } else if (obfuscatedElement.attr("href").contains("email-protection")) + obfuscatedElement.attr("href", "mailto:" + deobfuscatedEmail); obfuscatedElement.replaceWith(new TextNode(deobfuscatedEmail, "")); } @@ -339,7 +339,7 @@ public class ParseHelpers { * @param obfuscatedEmail CloudFlare-obfuscated email * @return deobfuscated email */ - private static String deobfuscateEmail(String obfuscatedEmail){ + private static String deobfuscateEmail(String obfuscatedEmail) { //Deobfuscate final StringBuilder stringBuilder = new StringBuilder(); final int r = Integer.parseInt(obfuscatedEmail.substring(0, 2), 16); @@ -348,7 +348,140 @@ public class ParseHelpers { stringBuilder.append(Character.toString((char) i)); } - Timber.i("Email deobfuscated."); + Timber.d("Email deobfuscated."); return stringBuilder.toString(); } + + public static String emojiTagToHtml(String emojiTagedString) { + HashMap tagToHtmlMap = new HashMap<>(); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":)"), Pattern.MULTILINE), "\"Smiley\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(";)"), Pattern.MULTILINE), "\"Wink\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":D"), Pattern.MULTILINE), "\"Cheesy\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(";D"), Pattern.MULTILINE), "\"Grin\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(">:("), Pattern.MULTILINE), "\"Angry\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":("), Pattern.MULTILINE), "\"Sad\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":o"), Pattern.MULTILINE), "\"Shocked\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("8))"), Pattern.MULTILINE), "\"Cool\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":???:"), Pattern.MULTILINE), "\"Huh\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":P"), Pattern.MULTILINE), "\"Tongue\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":-["), Pattern.MULTILINE), "\"Embarrassed\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":-X"), Pattern.MULTILINE), "\"Lips"); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":-\\"), Pattern.MULTILINE), "\"Undecided\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":-*"), Pattern.MULTILINE), "\"Kiss\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":'("), Pattern.MULTILINE), "\"Cry\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("<3"), Pattern.MULTILINE), "\"heart\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^locked^"), Pattern.MULTILINE), "\"kleidaria\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^rollover^"), Pattern.MULTILINE), "\"roll_over\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^redface^"), Pattern.MULTILINE), "\"redface\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^confused^"), Pattern.MULTILINE), "\"confused\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^innocent^"), Pattern.MULTILINE), "\"innocent\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^sleep^"), Pattern.MULTILINE), "\"sleep\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^sealed^"), Pattern.MULTILINE), "\"lips_sealed\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^cool^"), Pattern.MULTILINE), "\"cool\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^crazy^"), Pattern.MULTILINE), "\"crazy\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^mad^"), Pattern.MULTILINE), "\"mad\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^wav^"), Pattern.MULTILINE), "\"wav\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^binkybaby^"), Pattern.MULTILINE), "\"BinkyBaby\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^Police^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^dontknow^"), Pattern.MULTILINE), "\"DontKnow\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote(":angry4:"), Pattern.MULTILINE), "\"angry4\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^angryhot^"), Pattern.MULTILINE), "\"angryAndHot\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^angry^"), Pattern.MULTILINE), "\"angry\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^fouska^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^nysta^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^sfinaki^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^banghead^"), Pattern.MULTILINE), "\"bang_head\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^crybaby^"), Pattern.MULTILINE), "\"CryBaby\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^hello^"), Pattern.MULTILINE), "\"Hello\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^jerk^"), Pattern.MULTILINE), "\"jerk\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^nono^"), Pattern.MULTILINE), "\"NoNo\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^notworthy^"), Pattern.MULTILINE), "\"NotWorthy\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^off-topic^"), Pattern.MULTILINE), "\"Off-topic\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^puke^"), Pattern.MULTILINE), "\"Puke\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^shout^"), Pattern.MULTILINE), "\"Shout\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^slurp^"), Pattern.MULTILINE), "\"Slurp\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^superconfused^"), Pattern.MULTILINE), "\"SuperConfused\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^superinnocent^"), Pattern.MULTILINE), "\"SuperInnocent\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^cellPhone^"), Pattern.MULTILINE), "\"CellPhone\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^idiot^"), Pattern.MULTILINE), "\"Idiot\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^knuppel^"), Pattern.MULTILINE), "\"Knuppel\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^tickedOff^"), Pattern.MULTILINE), "\"TickedOff\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^peace^"), Pattern.MULTILINE), "\"Peace\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^suspicious^"), Pattern.MULTILINE), "\"Suspicious\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^caffine^"), Pattern.MULTILINE), "\"Caffine\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^argue^"), Pattern.MULTILINE), "\"argue\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^banned2^"), Pattern.MULTILINE), "\"banned2\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^banned^"), Pattern.MULTILINE), "\"banned\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^bath^"), Pattern.MULTILINE), "\"bath\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^beg^"), Pattern.MULTILINE), "\"beg\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^bluescreen^"), Pattern.MULTILINE), "\"bluescreen\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^boil^"), Pattern.MULTILINE), "\"boil\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^bye^"), Pattern.MULTILINE), "\"bye\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^callmerip^"), Pattern.MULTILINE), "\"callmerip\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^carnaval^"), Pattern.MULTILINE), "\"carnaval\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^clap^"), Pattern.MULTILINE), "\"clap\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^coffepot^"), Pattern.MULTILINE), "\"coffepot\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^crap^"), Pattern.MULTILINE), "\"crap\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^curses^"), Pattern.MULTILINE), "\"curses\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^funny^"), Pattern.MULTILINE), "\"funny\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^guitar^"), Pattern.MULTILINE), "\"guitar\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^kissy^"), Pattern.MULTILINE), "\"kissy\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^band^"), Pattern.MULTILINE), "\"band\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^ivres^"), Pattern.MULTILINE), "\"ivres\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^kaloe^"), Pattern.MULTILINE), "\"kaloe\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^kremala^"), Pattern.MULTILINE), "\"kremala\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^moon^"), Pattern.MULTILINE), "\"moon\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^mopping^"), Pattern.MULTILINE), "\"mopping\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^mountza^"), Pattern.MULTILINE), "\"mountza\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^pcsleep^"), Pattern.MULTILINE), "\"pcsleep\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^pinokio^"), Pattern.MULTILINE), "\"pinokio\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^poke^"), Pattern.MULTILINE), "\"poke\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^seestars^"), Pattern.MULTILINE), "\"seestars\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^sfyri^"), Pattern.MULTILINE), "\"sfyri\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^spam^"), Pattern.MULTILINE), "\"spam\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^super^"), Pattern.MULTILINE), "\"super\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^tafos^"), Pattern.MULTILINE), "\"tafos\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^tomato^"), Pattern.MULTILINE), "\"tomato\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^ytold^"), Pattern.MULTILINE), "\"ytold\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^beer^"), Pattern.MULTILINE), "\"beer\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^yue^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^eatpaper^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^fritz^"), Pattern.MULTILINE), "\"ο"); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^wade^"), Pattern.MULTILINE), "\"o"); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^lypi^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^aytoxeir^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^victory^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^filarakia^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^hat^"), Pattern.MULTILINE), "\"bonjour\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^miss^"), Pattern.MULTILINE), "\"bonjour2\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^rolfmao^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^lock^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^que^"), Pattern.MULTILINE), "\"question\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^shifty^"), Pattern.MULTILINE), "\"shifty\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^shy^"), Pattern.MULTILINE), "\"shy\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^music_listen^"), Pattern.MULTILINE), "\"music_listenning\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^bagface^"), Pattern.MULTILINE), "\"bag_face\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^rotate^"), Pattern.MULTILINE), "\"rotation\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^love^"), Pattern.MULTILINE), "\"love\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^speech^"), Pattern.MULTILINE), "\"speech\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^facepalm^"), Pattern.MULTILINE), "\"\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^shocked^"), Pattern.MULTILINE), "\"shocked\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^ex_shocked^"), Pattern.MULTILINE), "\"extremely_shocked\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^smurf^"), Pattern.MULTILINE), "\"smurf\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^monster^"), Pattern.MULTILINE), "\"monster\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^pig^"), Pattern.MULTILINE), "\"pig\""); + tagToHtmlMap.put(Pattern.compile(Pattern.quote("^lol^"), Pattern.MULTILINE), "\"lol\""); + + //Needs priority over the rest tags + final Pattern pattern = Pattern.compile(Pattern.quote("::)"), Pattern.MULTILINE); + Matcher matcher = pattern.matcher(emojiTagedString); + emojiTagedString = matcher.replaceAll("\"Roll"); + + for (Pattern patternKey : tagToHtmlMap.keySet()) { + matcher = patternKey.matcher(emojiTagedString); + emojiTagedString = matcher.replaceAll(tagToHtmlMap.get(patternKey)); + } + + return emojiTagedString; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java index a038c033..5787b36d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java +++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java @@ -3,6 +3,7 @@ package gr.thmmy.mthmmy.viewmodel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; + import gr.thmmy.mthmmy.model.Bookmark; public class BaseViewModel extends ViewModel { diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java index 25092578..c3f4dce5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java +++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java @@ -4,6 +4,7 @@ import android.os.AsyncTask; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; + import gr.thmmy.mthmmy.activities.shoutbox.SendShoutTask; import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxTask; import gr.thmmy.mthmmy.model.Shoutbox; diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java index 3ce30dfd..dee6d866 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java +++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java @@ -8,16 +8,17 @@ import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.RadioGroup; +import androidx.lifecycle.MutableLiveData; + import java.util.ArrayList; -import androidx.lifecycle.MutableLiveData; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditResult; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask; -import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyResult; +import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyTask; import gr.thmmy.mthmmy.activities.topic.tasks.RemoveVoteTask; import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask; import gr.thmmy.mthmmy.activities.topic.tasks.SubmitVoteTask; @@ -34,7 +35,7 @@ import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import timber.log.Timber; public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted, - PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished { + PrepareForReplyTask.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished { /** * topic state */ @@ -55,7 +56,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa private TopicTask currentTopicTask; private PrepareForEditTask currentPrepareForEditTask; - private PrepareForReply currentPrepareForReplyTask; + private PrepareForReplyTask currentPrepareForReplyTask; //callbacks for topic activity private TopicTask.TopicTaskObserver topicTaskObserver; @@ -64,7 +65,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa private ReplyTask.ReplyTaskCallbacks replyFinishListener; private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks; private EditTask.EditTaskCallbacks editTaskCallbacks; - private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks; + private PrepareForReplyTask.PrepareForReplyCallbacks prepareForReplyCallbacks; private ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener; private NetworkTask.OnNetworkTaskFinishedListener voteTaskFinishedListener; private ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener; @@ -176,7 +177,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa stopLoading(); setPageIndicatorIndex(pageCount, true); Timber.i("Preparing for reply"); - currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this, + currentPrepareForReplyTask = new PrepareForReplyTask(prepareForReplyCallbacks, this, replyPageUrl.getValue()); currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0])); } @@ -219,7 +220,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa PrepareForEditResult editResult = prepareForEditResult.getValue(); Timber.i("Editing post"); new EditTask(editTaskCallbacks, position).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, editResult.getCommitEditUrl(), message, - editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic()); + editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic(), editResult.getIcon()); } /** @@ -423,7 +424,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa this.editTaskCallbacks = editTaskCallbacks; } - public void setPrepareForReplyCallbacks(PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks) { + public void setPrepareForReplyCallbacks(PrepareForReplyTask.PrepareForReplyCallbacks prepareForReplyCallbacks) { this.prepareForReplyCallbacks = prepareForReplyCallbacks; } diff --git a/app/src/main/res/drawable/ic_attach_file_white_24dp.xml b/app/src/main/res/drawable/ic_attach_file_white_24dp.xml deleted file mode 100644 index 4834305b..00000000 --- a/app/src/main/res/drawable/ic_attach_file_white_24dp.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_cached_accent_24dp.xml b/app/src/main/res/drawable/ic_cached_accent_24dp.xml new file mode 100644 index 00000000..b4f3f7dd --- /dev/null +++ b/app/src/main/res/drawable/ic_cached_accent_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cancel_accent_24dp.xml b/app/src/main/res/drawable/ic_cancel_accent_24dp.xml new file mode 100644 index 00000000..4c1d6837 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_accent_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml b/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml new file mode 100644 index 00000000..1c3b206c --- /dev/null +++ b/app/src/main/res/drawable/ic_info_outline_warning_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/activity_board.xml b/app/src/main/res/layout/activity_board.xml index 9a51aa92..0a61658e 100644 --- a/app/src/main/res/layout/activity_board.xml +++ b/app/src/main/res/layout/activity_board.xml @@ -1,6 +1,5 @@ - + app:srcCompat="@drawable/ic_bookmark_false_accent_24dp" /> @@ -43,8 +42,7 @@ android:layout_marginTop="64dp" android:background="@color/background" android:scrollbars="none" - tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity"> - + tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity" /> + app:mpb_progressStyle="horizontal" /> + app:srcCompat="@drawable/ic_add_fab" /> - - diff --git a/app/src/main/res/layout/activity_downloads.xml b/app/src/main/res/layout/activity_downloads.xml index dc92a274..97e0afbc 100644 --- a/app/src/main/res/layout/activity_downloads.xml +++ b/app/src/main/res/layout/activity_downloads.xml @@ -48,7 +48,7 @@ app:mpb_indeterminateTint="@color/accent" app:mpb_progressStyle="horizontal"/> - + app:srcCompat="@drawable/ic_file_upload_white_24dp"/> diff --git a/app/src/main/res/layout/activity_topic.xml b/app/src/main/res/layout/activity_topic.xml index aaa13e4b..b61c41cf 100644 --- a/app/src/main/res/layout/activity_topic.xml +++ b/app/src/main/res/layout/activity_topic.xml @@ -45,8 +45,8 @@ android:layout_below="@id/appbar" android:layout_gravity="top|start" android:clipToPadding="false" - android:paddingBottom="4dp" android:paddingTop="4dp" + android:paddingBottom="4dp" android:scrollbars="none" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity" /> @@ -146,8 +146,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_marginBottom="50dp" android:layout_marginEnd="@dimen/fab_margins" + android:layout_marginBottom="50dp" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_reply" /> diff --git a/app/src/main/res/layout/activity_upload.xml b/app/src/main/res/layout/activity_upload.xml index 8639fd8c..0eea2f63 100644 --- a/app/src/main/res/layout/activity_upload.xml +++ b/app/src/main/res/layout/activity_upload.xml @@ -28,16 +28,16 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top|start" - android:background="@color/background" - android:paddingEnd="@dimen/activity_horizontal_margin" + android:background="@color/primary_light" android:paddingStart="@dimen/activity_horizontal_margin" + android:paddingEnd="@dimen/activity_horizontal_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="gr.thmmy.mthmmy.activities.upload.UploadActivity"> @@ -45,15 +45,16 @@ android:id="@+id/upload_spinners" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/background" + android:layout_marginTop="8dp" + android:background="@color/primary_light" android:orientation="vertical"> @@ -61,8 +62,8 @@ + android:layout_marginTop="6dp" + android:layout_marginBottom="6dp"> + + + + + + + + + + + android:layout_marginTop="6dp" + android:layout_marginBottom="6dp"> - - - - + + @@ -165,8 +193,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_marginBottom="@dimen/fab_margins" android:layout_marginEnd="@dimen/fab_margins" + android:layout_marginBottom="@dimen/fab_margins" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_file_upload_white_24dp" /> diff --git a/app/src/main/res/layout/activity_upload_fields_builder.xml b/app/src/main/res/layout/activity_upload_fields_builder.xml index 0583b601..601cdeeb 100644 --- a/app/src/main/res/layout/activity_upload_fields_builder.xml +++ b/app/src/main/res/layout/activity_upload_fields_builder.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top|start" - android:background="@color/background" + android:background="@color/primary_light" android:paddingEnd="@dimen/activity_horizontal_margin" android:paddingStart="@dimen/activity_horizontal_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" diff --git a/app/src/main/res/layout/activity_upload_file_list_row.xml b/app/src/main/res/layout/activity_upload_file_list_row.xml new file mode 100644 index 00000000..08b74a4a --- /dev/null +++ b/app/src/main/res/layout/activity_upload_file_list_row.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload_filename_info_popup.xml b/app/src/main/res/layout/activity_upload_filename_info_popup.xml new file mode 100644 index 00000000..bb6a9704 --- /dev/null +++ b/app/src/main/res/layout/activity_upload_filename_info_popup.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_upload_progress.xml b/app/src/main/res/layout/dialog_upload_progress.xml new file mode 100644 index 00000000..18444cb4 --- /dev/null +++ b/app/src/main/res/layout/dialog_upload_progress.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/editor_view_color_picker.xml b/app/src/main/res/layout/editor_view_color_picker.xml index b71214e1..be1ce295 100644 --- a/app/src/main/res/layout/editor_view_color_picker.xml +++ b/app/src/main/res/layout/editor_view_color_picker.xml @@ -1,11 +1,10 @@ - + android:layout_height="wrap_content" + android:background="@color/card_background"> - @@ -14,84 +13,84 @@ android:id="@+id/black" style="@style/PopupMenuItem.TopItem" android:text="@string/black" - android:textColor="@color/black"/> + android:textColor="@color/black" /> + android:textColor="@color/red" /> + android:textColor="@color/yellow" /> + android:textColor="@color/pink" /> + android:textColor="@color/green" /> + android:textColor="@color/orange" /> + android:textColor="@color/purple" /> + android:textColor="@color/blue" /> + android:textColor="@color/beige" /> + android:textColor="@color/brown" /> + android:textColor="@color/teal" /> + android:textColor="@color/navy" /> + android:textColor="@color/maroon" /> + android:textColor="@color/lime_green" /> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f2209027..6a76e274 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -18,6 +18,7 @@ #8B8B8B #FF9800 #FAA61A + #890d0d #FFFFFF #CCCCCC diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a663efa4..f14d2210 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ Inbox Info OK - Cancel + "Cancel" "To use mTHMMY you have to agree to our Privacy Policy by choosing one of the options below. Choose \"Yes, I want to help\", if you consent to the collection of anonymized data that will help us improve the app. Otherwise, choose \"Nope, leave me alone\". You can change your preferences any time through the app's Settings. user_consent_shared_preference_key @@ -133,12 +133,22 @@ Upload - Generate title and description + Generate fields Title + Upload as (filename) Description - Select file + Add files Take photo Select a category + Please follow the filename rules as\ndescribed + in this topic.\n + \nThis does not rename your local files. + Uploading + Uploading: %1$.2f Kbit/s, %2$d/%3$d KBytes + "Upload failed" + "Upload canceled" + "Resume in background" + "Retry" Select type of upload diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9d79d36d..2c0687b1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/values/uploads_courses.xml b/app/src/main/res/values/uploads_courses.xml new file mode 100644 index 00000000..51781f41 --- /dev/null +++ b/app/src/main/res/values/uploads_courses.xml @@ -0,0 +1,158 @@ + + + + + Ακουστική I:Ακουστική 1:Akoustiki_I + Ακουστική II:Ακουστική 2:Akoustiki_II + Ανάλυση Ηλεκτρικών Κυκλωμάτων με Υπολογιστή:Ανάλυση Ηλεκτρικ. Κυκλ. με Υπολογιστή:Analysi_Ilektr_Kykl + Ανάλυση Συστημάτων Ηλεκτρικής Ενέργειας:ΑΣΗΕ:ASHE + Ανάλυση Χρονοσειρών:Χρονοσειρές:Xronoseires + Ανάλυση και Σχεδίαση Αλγορίθμων:Αλγόριθμοι:Algorithms + Αναγνώριση Προτύπων:Αναγνώριση Προτύπων:protipa + Αναλογικές Τηλεπικοινωνίες (πρώην Τηλεπικοινωνιακά Συστήματα I):Αναλογικές Τηλεπ.:Anal_Tilep + Αντικειμενοστραφής Προγραμματισμός:Αντικειμενοστραφής:OOP + Αξιοπιστία Συστημάτων:Αξιοπιστία Συστημάτων:Aksiopistia_Systimaton + Αριθμητική Ανάλυση:Αριθμ. Ανάλυση:Arith_Anal + Αρχές Οικονομίας:Αρχές Οικονομίας:Arx_Oikonomias + Αρχές Παράλληλης Επεξεργασίας:Αρχές Παράλληλης Επεξεργασίας:Arxes_Parall_Epeksergasias + Αρχιτεκτονική Υπολογιστών:Αρχ. Υπολογιστών:Arx_Ypologiston + Ασαφή Συστήματα:Ασαφή:Asafi + Ασφάλεια Πληροφοριακών Συστημάτων:Ασφάλεια:Asfaleia + Ασύρματος Τηλεπικοινωνία I:Ασύρματος 1:Asyrmatos_I + Ασύρματος Τηλεπικοινωνία II:Ασύρματος 2:Asyrmatos_II + Βάσεις Δεδομένων:Βάσεις:Vaseis + Βιομηχανικά Ηλεκτρονικά:Βιομηχανικά Ηλεκτρονικά:Viomix_Ilektronika + Βιομηχανική Πληροφορική:Βιομηχανική Πληρ:Viomix_Plir + Βιοϊατρική Τεχνολογία:Βιοιατρική:Vioiatriki + Γεωηλεκτρομαγνητισμός:Γεωηλεκτρομαγνητισμός:Geoilektromagnitismos + Γραμμική Άλγεβρα:Γραμμ. Άλγεβρ.:Grammiki_Algevra + Γραφική με Υπολογιστές:Γραφική:Grafiki + Δίκτυα Τηλεπικοινωνιών:Δίκτυα Τηλέπ.:Diktya_Tilep + Δίκτυα Υπολογιστών I:Δίκτυα 1:Diktya_I + Δίκτυα Υπολογιστών II:Δίκτυα 2:Diktya_II + Διάδοση Η/Μ Κύματος II:Διάδοση 2:Diadosi_II + Διάδοση Ηλεκτρομαγνητικού Κύματος I (πρώην Πεδίο III):Διάδοση 1:Diadosi_I + Διακριτά Μαθηματικά:Διακριτά Μαθηματικά:Diakrita + Διακριτά μαθηματικά:Διακριτά Μαθηματικά:Diakrita + Διανεμημένη Παραγωγή:Διανεμημένη Παραγωγή:Dian_Paragogi + Διατάξεις Υψηλών Συχνοτήτων:ΔΥΣ:DYS + Διαφορικές Εξισώσεις:Διαφορικές:Diaforikes + Διαχείριση Συστημάτων Ηλεκτρικής Ενέργειας:ΔΣΗΕ:DSHE + Δομές Δεδομένων:Δομ. Δεδομ.:Domes_Dedomenon + Δομημένος Προγραμματισμός:Δομ. Προγραμμ.:C + Ειδικά Κεφάλαια Διαφορικών Εξισώσεων:Ειδικά Κεφάλαια Διαφορικών Εξισώσεων:Eidika_Kef_Diaf_Eksis + Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου I:Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου I:Eidika_Kef_HM_Pediou_I + Ειδικά Κεφάλαια Συστημάτων Ηλεκτρικής Ενέργειας:ΕΚΣΗΕ:EKSHE + Ειδικές Αρχιτεκτονικές Υπολογιστών:Ειδικές Αρχιτεκτονικές Υπολογιστών:Eidikes_Arx_Ypolog + Ειδικές Κεραίες, Σύνθεση Κεραιών:Ειδικές Κεραίες, Σύνθεση Κεραιών:Eidikes_Keraies + Εισαγωγή στην Ενεργειακή Τεχνολογία I:ΕΕΤ 1:EET_I + Εισαγωγή στην Ενεργειακή Τεχνολογία II:ΕΕΤ2:EET_II + Εισαγωγή στην Πολιτική Οικονομία:Πολιτική Οικονομία:Polit_Oik + Εισαγωγή στις εφαρμογές Πυρηνικής Τεχνολογίας:Εισ. Πυρηνικη Τεχν.:Intro_Pyriniki_Texn + Ενσωματωμένα Συστήματα Πραγματικού Χρόνου:Ενσωματωμένα:Enswmatwmena + Επιχειρησιακή Έρευνα:Επιχειρησιακή Έρευνα:Epixeirisiaki + Ευρυζωνικά Δίκτυα:Ευρυζωνικά:Evryzonika + Ευφυή Συστήματα Ρομπότ:Ευφυή:eufuh + Εφαρμογές Τηλεπικοινωνιακών Διατάξεων:Εφαρμογές Τηλεπ. Διατάξεων:Efarm_Tilep_Diatakseon + Εφαρμοσμένα Μαθηματικά I:Εφαρμοσμένα 1:Efarmosmena_Math_I + Εφαρμοσμένα Μαθηματικά II:Εφαρμοσμένα 2:Efarmosmena_Math_II + Εφαρμοσμένη Θερμοδυναμική:Θερμοδυναμική:Thermodynamiki + Ηλεκτρακουστική I:Ηλεκτρακουστική 1:Ilektrakoustiki_I + Ηλεκτρακουστική II:Ηλεκτρακουστική 2:Ilektrakoustiki_II + Ηλεκτρικά Κυκλώματα I:Κυκλώματα 1:Kyklomata_I + Ηλεκτρικά Κυκλώματα II:Κυκλώματα 2:Kyklomata_II + Ηλεκτρικά Κυκλώματα III:Κυκλώματα 3:Kyklomata_I + Ηλεκτρικές Μετρήσεις I:Μετρήσεις 1:Metriseis_I + Ηλεκτρικές Μετρήσεις II:Μετρήσεις 2:Metriseis_II + Ηλεκτρικές Μηχανές I:Μηχανές I:Mixanes_I + Ηλεκτρικές Μηχανές Α\':Μηχανές Α:Mixanes_A + Ηλεκτρικές Μηχανές Β\':Μηχανές Β:Mixanes_B + Ηλεκτρικές Μηχανές Γ\':Μηχανές Γ:Mixanes_C + Ηλεκτρική Οικονομία:Ηλεκτρική Οικονομία:Ilektr_Oikonomia + Ηλεκτρολογικά Υλικά:Ηλεκτρ. Υλικά:Ylika + Ηλεκτρομαγνητική Συμβατότητα:H/M Συμβατότητα:HM_Symvatotita + Ηλεκτρομαγνητικό Πεδίο I:Πεδίο 1:Pedio_I + Ηλεκτρομαγνητικό Πεδίο II:Πεδίο 2:Pedio_II + Ηλεκτρονικά Ισχύος I:Ισχύος 1:Isxyos_I + Ηλεκτρονικά Ισχύος II:Ισχύος 2:Isxyos_II + Ηλεκτρονικές Διατάξεις και Μετρήσεις:Ηλεκτρονικές Διατάξεις και Μετρήσεις:Ilektron_Diatakseis_Metriseis + Ηλεκτρονική I:Ηλεκτρονική 1:Ilektroniki_I + Ηλεκτρονική II:Ηλεκτρονική 2:Ilektroniki_II + Ηλεκτρονική III:Ηλεκτρονική 3:Ilektroniki_III + Ημιαγωγά Υλικά: Θεωρία-Διατάξεις:Ημιαγωγά Υλικά:Imiagoga_Ylika + Θεωρία Πιθανοτήτων και Στατιστική:Πιθανότητες:Pithanotites + Θεωρία Πληροφοριών:Θεωρία Πληρ.:Theoria_Plir + Θεωρία Σημάτων και Γραμμικών Συστημάτων:Σήματα & Συστήματα:Analog_Sima + Θεωρία Σκέδασης:Σκέδαση:Skedasi + Θεωρία Υπολογισμών και Αλγορίθμων:ΘΥΑ:THYA + Θεωρία και Τεχνολογία Πυρηνικών Αντιδραστήρων:Τεχνολογία Αντιδραστήρων:Texn_Antidrasthron + Κβαντική Φυσική:Κβαντική:Kvantiki + Κινητές και Δορυφορικές Επικοινωνίες:Κινητές & Δορυφορικές Επικοινωνίες:Kinites_Doryforikes_Epik + Λειτουργικά Συστήματα:Λειτουργικά:OS + Λογική Σχεδίαση:Λογική Σχεδίαση:Logiki_Sxediasi + Λογισμός I:Λογισμός 1:Logismos_I + Λογισμός II:Λογισμός 2:Logismos_II + Μετάδοση Θερμότητας:Μετάδοση Θερμ.:Metadosi_Therm + Μικροεπεξεργαστές και Περιφερειακά:Μίκρο 2:Mikro_II + Μικροκυματική Τηλεπισκόπηση:Τηλεπισκόπηση:Tilepiskopisi + Μικροκύματα I:Μικροκύματα 1:Mikrokymata_I + Μικροκύματα II:Μικροκύματα 2:Mikrokymata_II + Οπτικές Επικοινωνίες:Οπτικές Τηλεπ.:Optikes_Tilep + Οπτική I:Οπτική 1:Optiki_I + Οπτική II:Οπτική 2:Optiki_II + Οργάνωση Υπολογιστών:Οργάνωση Υπολ.:Org_Ypol + Οργάνωση και Διοίκηση Εργοστασίων:Οργάνωση και Διοίκηση Εργοστασίων:Organ_Dioik_Ergostasion + Παράλληλα και Κατανεμημένα Συστήματα:Παράλληλα:Parallila + Προγραμματιζόμενα Κυκλώματα ASIC:ASIC:ASIC + Προγραμματιστικές Τεχνικές:Προγραμματ. Τεχν.:CPP + Προηγμένες Τεχνικές Επεξεργασίας Σήματος:ΠΤΕΣ:PTES + Προσομοίωση και Μοντελοποίηση Συστημάτων:Μοντελοποίηση:Montelopoiisi + Ρομποτική:Ρομποτική:Robotiki + Σήματα και Συστήματα:Σήματα & Συστήματα:Analog_Sima + Σερβοκινητήρια Συστήματα:Σέρβο:Servo + Σταθμοί Παραγωγής Ηλεκτρικής Ενέργειας:ΣΠΗΕ:SPHE + Στοχαστικά Σήματα και Διαδικασίες:Στοχαστικό:Stochastic + Στοχαστικό Σήμα:Στοχαστικό:Stochastic + Συστήματα Αυτομάτου Ελέγχου I:ΣΑΕ 1:SAE_I + Συστήματα Αυτομάτου Ελέγχου II:ΣΑΕ 2:SAE_II + Συστήματα Αυτομάτου Ελέγχου III:ΣΑΕ 3:SAE_III + Συστήματα Ηλεκτρικής Ενέργειας I:ΣΗΕ 1:SHE_I + Συστήματα Ηλεκτρικής Ενέργειας II:ΣΗΕ 2:SHE_II + Συστήματα Ηλεκτρικής Ενέργειας III:ΣΗΕ 3:SHE_III + Συστήματα Ηλεκτροκίνησης:Ηλεκτροκίνηση:Ilektrokinisi + Συστήματα Μικροϋπολογιστών:Μίκρο 1:Mikro_I + Συστήματα Πολυμέσων και Εικονική Πραγματικότητα:Πολυμέσα:Polymesa + Συστήματα Υπολογιστών (Υπολογιστικά Συστήματα):Συσ. Υπολογιστών:Sys_Ypologiston + Σχεδίαση Συστημάτων VLSI:VLSI:VLSI + Σύνθεση Ενεργών και Παθητικών Κυκλωμάτων:Σύνθεση:Synthesi + Σύνθεση Τηλεπικοινωνιακών Διατάξεων:Σύνθεση Τηλεπ. Διατάξεων:Synth_Tilep_Diatakseon + Τεχνικές Βελτιστοποίησης:Βελτιστοποίηση:Veltistopoiisi + Τεχνικές Κωδικοποίησης:Τεχνικές Κωδικοποίησης:Texn_Kodikopoiisis + Τεχνικές Σχεδίασης με Η/Υ:Σχέδιο:sxedio + Τεχνικές μη Καταστρεπτικών Δοκιμών:Μη Καταστρεπτικές Δοκιμές:Non_Destructive_Tests + Τεχνική Μηχανική:Τεχν. Μηχαν.:Texn_Mixan + Τεχνολογία Ήχου και Εικόνας:Τεχνολογία Ήχου και Εικόνας:Texn_Ixou_Eikonas + Τεχνολογία Ηλεκτροτεχνικών Υλικών:Ηλεκτροτεχνικά Υλικά:Ilektrotexnika_Ylika + Τεχνολογία Λογισμικού:Τεχνολογία Λογισμικού:SE + Τηλεοπτικά Συστήματα:Τηλεοπτικά:Tileoptika + Τηλεπικοινωνιακά Συστήματα I:Τηλεπικοινωνιακά I:Tilepikoinoniaka_I + Τηλεπικοινωνιακά Συστήματα II:Τηλεπικοινωνιακά II:Tilepikoinoniaka_II + Τηλεπικοινωνιακή Ηλεκτρονική:Τηλεπ. Ηλεκτρ.:Tilep_Ilektr + Υπολογιστικές Μέθοδοι στα Ενεργειακά Συστήματα:ΥΜΕΣ:YMES + Υπολογιστικός Ηλεκτρομαγνητισμός:Υπολογιστικός Η/Μ:Ypologistikos_HM + Υψηλές Τάσεις I:Υψηλές 1:Ypsiles_I + Υψηλές Τάσεις II:Υψηλές 2:Ypsiles_II + Υψηλές Τάσεις III:Υψηλές 3:Ypsiles_III + Υψηλές Τάσεις 4:Υψηλές 4:Ypsiles_IV + Φυσική I:Φυσική 1:Fysiki_I + Φωτονική Τεχνολογία:Φωτονική:Fotoniki + Ψηφιακά Συστήματα I:Ψηφιακά 1:Psifiaka_I + Ψηφιακά Συστήματα II:Ψηφιακά 2:Psifiaka_II + Ψηφιακά Συστήματα III:Ψηφιακά 3:Psifiaka_III + Ψηφιακά Φίλτρα:Φίλτρα:Filtra + Ψηφιακές Τηλεπικοινωνίες I:Ψηφιακές Τηλεπ. 1:Psif_Tilep_I + Ψηφιακές Τηλεπικοινωνίες II:Ψηφιακές Τηλεπ. 2:Psif_Tilep_II + Ψηφιακή Επεξεργασία Εικόνας:ΨΕΕ:PSEE + Ψηφιακή Επεξεργασία Σήματος:ΨΕΣ:PSES + + \ No newline at end of file diff --git a/app/src/main/res/xml-v26/app_preferences_user.xml b/app/src/main/res/xml-v26/app_preferences_user.xml index fe301eb3..0bda7493 100644 --- a/app/src/main/res/xml-v26/app_preferences_user.xml +++ b/app/src/main/res/xml-v26/app_preferences_user.xml @@ -28,7 +28,7 @@ app:iconSpaceReserved="false" /> - + - +