@ -0,0 +1,76 @@ |
|||
# Privacy Policy |
|||
|
|||
*Effective date: 13/10/2018* |
|||
|
|||
Thmmy No Life ("us", "we", or "our") developed the mTHMMY mobile app (the "App") as an open source application. It is provided by us at no cost and is intended for use as is. |
|||
|
|||
As a user of the App, your privacy is protected and we feel a strong commitment to protect your privacy. The purpose of this Privacy Policy is to inform you about the collection, use and disclosure of your data, and the choices you have associated with them. |
|||
|
|||
## Data processing |
|||
|
|||
To be able to offer you all functions and services of the App in the most convenient way possible and to continuously improve it, we use a number of different cloud services. This means we will transfer your data to a third party – the cloud services provider. |
|||
|
|||
Google Ireland Limited ("Google"), with offices at Gordon House, Barrow Street, Dublin 4, Ireland and, more specifically, Firebase, a Google subsidiary with its registered office in San Francisco, CA, U.S.A., is a third party – the cloud services provider – that stores and processes your data on our behalf (the "processor", as defined in Article 4, GDPR). |
|||
|
|||
### Location of processing |
|||
|
|||
The majority of Firebase services run on global Google infrastructure. They could process data at any of the Google Cloud Platform locations or Google data center locations, within or outside the European Union (EU). |
|||
|
|||
The Commission Decision (EU) 2016/1250 of 12.07.2016 allows the transfer of data from an EU controller or processor of orders to organizations in the US that have committed themselves to adhere to the framework principles of the EU-US Privacy Shield (<https://www.privacyshield.gov>), including the additional principles, by way of self-certification with the US Department of Commerce. Google is subject to these principles through self-certification with the U.S. Department of Commerce. |
|||
|
|||
### General Information |
|||
|
|||
* The Firebase services that we use collect and further process anonymized information, data with no personally identifiable information which can be used to trace its source so that the people whom it describes can remain anonymous. These data are not subject to the same treatment as are personal data, particularly with regards to any desire you might have for accessing it, rectifying it or erasing it (Article 11, GDPR). |
|||
* Firebase uses the Instance ID of your mobile device to identify individual installations of this mobile app. Since each Instance ID is unique to a particular application and device, they give Firebase a way to refer to specific instances of the App. |
|||
* Your IP address transmitted by the App in the context of Firebase will not be merged with other collected data. |
|||
* Legal basis for the use of Firebase is Article 6 Par. 1 S. 1 letter (a) or (f), GDPR. |
|||
|
|||
The use of each Firebase service is explained below: |
|||
|
|||
### Firebase Cloud Messaging |
|||
|
|||
* **Purpose**: This service is essential for the funcionality of the App. It uses anonymous push notification tokens in order to determine which devices to deliver the appropriate messages to. |
|||
* **Data collected**: Instance IDs. |
|||
* **Consent**: You will be prompted to agree to the use of this service when you open the App for the first time. |
|||
* **Retention**: Firebase retains Instance IDs until we make an API call for deletion. After the call, data are removed from live and backup systems within 180 days. |
|||
|
|||
### Firebase Crashlytics |
|||
|
|||
* **Purpose**: This service automatically collects and delivers analyses of errors and system crashes in real time and displays them in the Firebase Console. This helps us maintain the App and improve its stability. |
|||
* **Data collected**: Instance IDs and crash reports with information about register codes and your device, e.g. type of device and version of operating system. For more information: <https://try.crashlytics.com/security/> |
|||
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable it from the Settings inside the App. |
|||
* **Retention**: Crash traces and their associated identifiers are kept for 90 days. |
|||
|
|||
### Google Analytics for Firebase |
|||
|
|||
* **Purpose**: This service provides analytics and attribution information for statistical purposes. The precise information collected can vary by the device and environment. |
|||
* **Data collected**: Instance IDs, Android IDs, Analytics App Instance IDs and custom events created by us. For more information: <https://support.google.com/firebase/answer/6318039> |
|||
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable (and clear all collected data) from the Settings inside the App. |
|||
* **Retention**: ID-associated data are retained for 60 days. Aggregate reporting and campaign data are retained without automatic expiration. |
|||
|
|||
You can find further information on the data use by Google through Firebase following the links below: |
|||
<https://firebase.google.com/terms/> |
|||
<https://firebase.google.com/terms/data-processing-terms> |
|||
<https://firebase.google.com/support/privacy/> |
|||
<https://firebase.google.com/support/privacy/manage-iids> |
|||
|
|||
## Other data |
|||
|
|||
* When downloading the mobile app, the necessary information is transferred to the App Store (Google Play), i.e. in particular the name, e-mail address and customer number of your customer account, time of download, payment information and the individual device identification number. We have no influence on this data collection and shall not be responsible for it. We only process the data if it is necessary for downloading the mobile app to your mobile device. |
|||
* The App provides functionality to login to thmmy.gr through an encrypted connection. This process generates session cookies that are stored on your device. Those cookies contain the same information as those that would be generated if you logged in using a web browser and we have no influence on them. Any other personal information that the App collects from you from thmmy.gr is only stored locally and never transmitted. You can delete all the data related to thmmy.gr anytime you wish, by logging out or clearing the App's data from your device's Settings. |
|||
|
|||
## About thmmy.gr |
|||
|
|||
We have neither influence, nor responsibility on anything you read, write, download or upload by interacting with thmmy.gr through the App. For more information you can also read the Terms of Service of thmmy.gr at <https://www.thmmy.gr/smf/index.php?topic=52522>. |
|||
|
|||
## Third-Party Links |
|||
|
|||
mTHMMY may contain links to third-party websites. Any access to and use of such linked websites is not governed by this Policy, but instead is governed by the privacy policies of those third party websites. We are not responsible for the information practices of such third party websites. |
|||
|
|||
## Policy Updates |
|||
|
|||
We may update this Privacy Policy from time to time and, thus, you are advised to review it periodically. The most recent version of our Privacy Policy can be found at <https://github.com/ThmmyNoLife/mTHMMY/blob/develop/PRIVACY.md>. |
|||
|
|||
## Contact Us |
|||
|
|||
If you have any questions about our Privacy Policy, please contact us at [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com). |
@ -0,0 +1,76 @@ |
|||
# Privacy Policy |
|||
|
|||
*Effective date: 13/10/2018* |
|||
|
|||
Thmmy No Life ("us", "we", or "our") developed the mTHMMY mobile app (the "App") as an open source application. It is provided by us at no cost and is intended for use as is. |
|||
|
|||
As a user of the App, your privacy is protected and we feel a strong commitment to protect your privacy. The purpose of this Privacy Policy is to inform you about the collection, use and disclosure of your data, and the choices you have associated with them. |
|||
|
|||
## Data processing |
|||
|
|||
To be able to offer you all functions and services of the App in the most convenient way possible and to continuously improve it, we use a number of different cloud services. This means we will transfer your data to a third party – the cloud services provider. |
|||
|
|||
Google Ireland Limited ("Google"), with offices at Gordon House, Barrow Street, Dublin 4, Ireland and, more specifically, Firebase, a Google subsidiary with its registered office in San Francisco, CA, U.S.A., is a third party – the cloud services provider – that stores and processes your data on our behalf (the "processor", as defined in Article 4, GDPR). |
|||
|
|||
### Location of processing |
|||
|
|||
The majority of Firebase services run on global Google infrastructure. They could process data at any of the Google Cloud Platform locations or Google data center locations, within or outside the European Union (EU). |
|||
|
|||
The Commission Decision (EU) 2016/1250 of 12.07.2016 allows the transfer of data from an EU controller or processor of orders to organizations in the US that have committed themselves to adhere to the framework principles of the EU-US Privacy Shield (<https://www.privacyshield.gov>), including the additional principles, by way of self-certification with the US Department of Commerce. Google is subject to these principles through self-certification with the U.S. Department of Commerce. |
|||
|
|||
### General Information |
|||
|
|||
* The Firebase services that we use collect and further process anonymized information, data with no personally identifiable information which can be used to trace its source so that the people whom it describes can remain anonymous. These data are not subject to the same treatment as are personal data, particularly with regards to any desire you might have for accessing it, rectifying it or erasing it (Article 11, GDPR). |
|||
* Firebase uses the Instance ID of your mobile device to identify individual installations of this mobile app. Since each Instance ID is unique to a particular application and device, they give Firebase a way to refer to specific instances of the App. |
|||
* Your IP address transmitted by the App in the context of Firebase will not be merged with other collected data. |
|||
* Legal basis for the use of Firebase is Article 6 Par. 1 S. 1 letter (a) or (f), GDPR. |
|||
|
|||
The use of each Firebase service is explained below: |
|||
|
|||
### Firebase Cloud Messaging |
|||
|
|||
* **Purpose**: This service is essential for the funcionality of the App. It uses anonymous push notification tokens in order to determine which devices to deliver the appropriate messages to. |
|||
* **Data collected**: Instance IDs. |
|||
* **Consent**: You will be prompted to agree to the use of this service when you open the App for the first time. |
|||
* **Retention**: Firebase retains Instance IDs until we make an API call for deletion. After the call, data are removed from live and backup systems within 180 days. |
|||
|
|||
### Firebase Crashlytics |
|||
|
|||
* **Purpose**: This service automatically collects and delivers analyses of errors and system crashes in real time and displays them in the Firebase Console. This helps us maintain the App and improve its stability. |
|||
* **Data collected**: Instance IDs and crash reports with information about register codes and your device, e.g. type of device and version of operating system. For more information: <https://try.crashlytics.com/security/> |
|||
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable it from the Settings inside the App. |
|||
* **Retention**: Crash traces and their associated identifiers are kept for 90 days. |
|||
|
|||
### Google Analytics for Firebase |
|||
|
|||
* **Purpose**: This service provides analytics and attribution information for statistical purposes. The precise information collected can vary by the device and environment. |
|||
* **Data collected**: Instance IDs, Android IDs, Analytics App Instance IDs and custom events created by us. For more information: <https://support.google.com/firebase/answer/6318039> |
|||
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable (and clear all collected data) from the Settings inside the App. |
|||
* **Retention**: ID-associated data are retained for 60 days. Aggregate reporting and campaign data are retained without automatic expiration. |
|||
|
|||
You can find further information on the data use by Google through Firebase following the links below: |
|||
<https://firebase.google.com/terms/> |
|||
<https://firebase.google.com/terms/data-processing-terms> |
|||
<https://firebase.google.com/support/privacy/> |
|||
<https://firebase.google.com/support/privacy/manage-iids> |
|||
|
|||
## Other data |
|||
|
|||
* When downloading the mobile app, the necessary information is transferred to the App Store (Google Play), i.e. in particular the name, e-mail address and customer number of your customer account, time of download, payment information and the individual device identification number. We have no influence on this data collection and shall not be responsible for it. We only process the data if it is necessary for downloading the mobile app to your mobile device. |
|||
* The App provides functionality to login to thmmy.gr through an encrypted connection. This process generates session cookies that are stored on your device. Those cookies contain the same information as those that would be generated if you logged in using a web browser and we have no influence on them. Any other personal information that the App collects from you from thmmy.gr is only stored locally and never transmitted. You can delete all the data related to thmmy.gr anytime you wish, by logging out or clearing the App's data from your device's Settings. |
|||
|
|||
## About thmmy.gr |
|||
|
|||
We have neither influence, nor responsibility on anything you read, write, download or upload by interacting with thmmy.gr through the App. For more information you can also read the Terms of Service of thmmy.gr at <https://www.thmmy.gr/smf/index.php?topic=52522>. |
|||
|
|||
## Third-Party Links |
|||
|
|||
mTHMMY may contain links to third-party websites. Any access to and use of such linked websites is not governed by this Policy, but instead is governed by the privacy policies of those third party websites. We are not responsible for the information practices of such third party websites. |
|||
|
|||
## Policy Updates |
|||
|
|||
We may update this Privacy Policy from time to time and, thus, you are advised to review it periodically. The most recent version of our Privacy Policy can be found at <https://github.com/ThmmyNoLife/mTHMMY/blob/develop/PRIVACY.md>. |
|||
|
|||
## Contact Us |
|||
|
|||
If you have any questions about our Privacy Policy, please contact us at [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com). |
@ -0,0 +1,110 @@ |
|||
package gr.thmmy.mthmmy.activities.create_content; |
|||
|
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.os.Bundle; |
|||
import android.preference.PreferenceManager; |
|||
import android.support.design.widget.TextInputLayout; |
|||
import android.text.InputType; |
|||
import android.text.TextUtils; |
|||
import android.view.View; |
|||
import android.view.inputmethod.EditorInfo; |
|||
import android.widget.Toast; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.settings.SettingsActivity; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.editorview.EditorView; |
|||
import gr.thmmy.mthmmy.editorview.EmojiKeyboard; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import timber.log.Timber; |
|||
|
|||
public class CreateContentActivity extends BaseActivity implements NewTopicTask.NewTopicTaskCallbacks { |
|||
|
|||
public final static String EXTRA_NEW_TOPIC_URL = "new-topic-extra"; |
|||
|
|||
private EditorView contentEditor; |
|||
private EmojiKeyboard emojiKeyboard; |
|||
private TextInputLayout subjectInput; |
|||
private MaterialProgressBar progressBar; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_create_content); |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = findViewById(R.id.toolbar); |
|||
toolbar.setTitle("Create topic"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
progressBar = findViewById(R.id.progressBar); |
|||
|
|||
Intent callingIntent = getIntent(); |
|||
String newTopicUrl = callingIntent.getStringExtra(EXTRA_NEW_TOPIC_URL); |
|||
|
|||
emojiKeyboard = findViewById(R.id.emoji_keyboard); |
|||
|
|||
subjectInput = findViewById(R.id.subject_input); |
|||
subjectInput.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); |
|||
subjectInput.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); |
|||
|
|||
contentEditor = findViewById(R.id.main_content_editorview); |
|||
contentEditor.setEmojiKeyboard(emojiKeyboard); |
|||
emojiKeyboard.registerEmojiInputField(contentEditor); |
|||
contentEditor.setOnSubmitListener(v -> { |
|||
if (newTopicUrl != null) { |
|||
if (TextUtils.isEmpty(subjectInput.getEditText().getText())) { |
|||
subjectInput.setError("Required"); |
|||
return; |
|||
} |
|||
if (TextUtils.isEmpty(contentEditor.getText())) { |
|||
contentEditor.setError("Required"); |
|||
return; |
|||
} |
|||
boolean includeAppSignature = true; |
|||
SessionManager sessionManager = BaseActivity.getSessionManager(); |
|||
if (sessionManager.isLoggedIn()) { |
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
|||
includeAppSignature = prefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true); |
|||
} |
|||
emojiKeyboard.setVisibility(View.GONE); |
|||
|
|||
new NewTopicTask(this, includeAppSignature).execute(newTopicUrl, subjectInput.getEditText().getText().toString(), |
|||
contentEditor.getText().toString()); |
|||
} |
|||
}); |
|||
} |
|||
@Override |
|||
public void onBackPressed() { |
|||
if (emojiKeyboard.getVisibility() == View.VISIBLE) { |
|||
emojiKeyboard.setVisibility(View.GONE); |
|||
} else { |
|||
super.onBackPressed(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onNewTopicTaskStarted() { |
|||
Timber.i("New topic creation started"); |
|||
progressBar.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onNewTopicTaskFinished(boolean success) { |
|||
progressBar.setVisibility(View.INVISIBLE); |
|||
if (success) { |
|||
Timber.i("New topic created successfully"); |
|||
finish(); |
|||
} else { |
|||
Timber.w("New topic creation failed"); |
|||
Toast.makeText(getBaseContext(), "Failed to create new topic!", Toast.LENGTH_LONG).show(); |
|||
finish(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
package gr.thmmy.mthmmy.activities.create_content; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import okhttp3.MultipartBody; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.RequestBody; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus; |
|||
|
|||
public class NewTopicTask extends AsyncTask<String, Void, Boolean> { |
|||
|
|||
private NewTopicTaskCallbacks listener; |
|||
private boolean includeAppSignature; |
|||
|
|||
public NewTopicTask(NewTopicTaskCallbacks listener, boolean includeAppSignature){ |
|||
this.listener = listener; |
|||
this.includeAppSignature = includeAppSignature; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
listener.onNewTopicTaskStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected Boolean doInBackground(String... strings) { |
|||
Request request = new Request.Builder() |
|||
.url(strings[0] + ";wap2") |
|||
.build(); |
|||
|
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
|
|||
Document document; |
|||
String seqnum, sc, topic, createTopicUrl; |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
|
|||
seqnum = document.select("input[name=seqnum]").first().attr("value"); |
|||
sc = document.select("input[name=sc]").first().attr("value"); |
|||
topic = document.select("input[name=topic]").first().attr("value"); |
|||
createTopicUrl = document.select("form").first().attr("action"); |
|||
|
|||
final String appSignature = "\n[right][size=7pt][i]sent from [url=https://play.google.com/store/apps/" + |
|||
"details?id=gr.thmmy.mthmmy]mTHMMY[/url] [/i][/size][/right]"; |
|||
|
|||
RequestBody postBody = new MultipartBody.Builder() |
|||
.setType(MultipartBody.FORM) |
|||
.addFormDataPart("message", strings[2] + (includeAppSignature ? appSignature : "")) |
|||
.addFormDataPart("seqnum", seqnum) |
|||
.addFormDataPart("sc", sc) |
|||
.addFormDataPart("subject", strings[1]) |
|||
.addFormDataPart("topic", topic) |
|||
.build(); |
|||
|
|||
Request post = new Request.Builder() |
|||
.url(createTopicUrl) |
|||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") |
|||
.post(postBody) |
|||
.build(); |
|||
|
|||
try { |
|||
client.newCall(post).execute(); |
|||
Response response2 = client.newCall(post).execute(); |
|||
switch (replyStatus(response2)) { |
|||
case SUCCESSFUL: |
|||
BaseApplication.getInstance().logFirebaseAnalyticsEvent("new_topic_creation", null); |
|||
return true; |
|||
default: |
|||
Timber.e("Malformed post. Request string: %s", post.toString()); |
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Boolean success) { |
|||
listener.onNewTopicTaskFinished(success); |
|||
} |
|||
|
|||
public interface NewTopicTaskCallbacks { |
|||
void onNewTopicTaskStarted(); |
|||
void onNewTopicTaskFinished(boolean success); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
package gr.thmmy.mthmmy.activities.settings; |
|||
|
|||
import android.os.Bundle; |
|||
import android.support.v4.app.FragmentTransaction; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
|
|||
public class SettingsActivity extends BaseActivity { |
|||
public static final String DEFAULT_HOME_TAB = "pref_app_main_default_tab_key"; |
|||
public static final String NOTIFICATION_LED_KEY = "pref_notification_led_enable_key"; |
|||
public static final String NOTIFICATION_VIBRATION_KEY = "pref_notification_vibration_enable_key"; |
|||
public static final String POSTING_APP_SIGNATURE_ENABLE_KEY = "pref_posting_app_signature_enable_key"; |
|||
public static final String UPLOADING_APP_SIGNATURE_ENABLE_KEY = "pref_uploading_app_signature_enable_key"; |
|||
|
|||
private SettingsFragment preferenceFragment; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_settings); |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = findViewById(R.id.toolbar); |
|||
toolbar.setTitle("Settings"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(SETTINGS_ID); |
|||
|
|||
preferenceFragment = SettingsFragment.newInstance(sessionManager.isLoggedIn()); |
|||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); |
|||
fragmentTransaction.add(R.id.pref_container, preferenceFragment); |
|||
fragmentTransaction.commit(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(SETTINGS_ID); |
|||
super.onResume(); |
|||
if (preferenceFragment != null) |
|||
preferenceFragment.updateUserLoginState(sessionManager.isLoggedIn()); |
|||
} |
|||
} |
@ -0,0 +1,214 @@ |
|||
package gr.thmmy.mthmmy.activities.settings; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.media.RingtoneManager; |
|||
import android.net.Uri; |
|||
import android.os.Bundle; |
|||
import android.provider.Settings; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v7.preference.ListPreference; |
|||
import android.support.v7.preference.Preference; |
|||
import android.support.v7.preference.PreferenceFragmentCompat; |
|||
import android.view.View; |
|||
import android.widget.Toast; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import timber.log.Timber; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB; |
|||
|
|||
public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { |
|||
private enum PREFS_TYPE { |
|||
NOT_SET, USER, GUEST |
|||
} |
|||
|
|||
public static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey"; |
|||
|
|||
//Preferences xml keys
|
|||
private static final String SELECTED_NOTIFICATIONS_SOUND = "pref_notifications_select_sound_key"; |
|||
private static final String POSTING_CATEGORY = "pref_category_posting_key"; |
|||
private static final String UPLOADING_CATEGORY = "pref_category_uploading_key"; |
|||
|
|||
//SharedPreferences keys
|
|||
private static final int REQUEST_CODE_ALERT_RINGTONE = 2; |
|||
public static final String SETTINGS_SHARED_PREFS = "settingsSharedPrefs"; |
|||
public static final String SELECTED_RINGTONE = "selectedRingtoneKey"; |
|||
private static final String SILENT_SELECTED = "STFU"; |
|||
|
|||
private SharedPreferences settingsFile; |
|||
|
|||
private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET; |
|||
private boolean isLoggedIn = false; |
|||
private ArrayList<String> defaultHomeTabEntries = new ArrayList<>(); |
|||
private ArrayList<String> defaultHomeTabValues = new ArrayList<>(); |
|||
|
|||
public static SettingsFragment newInstance(boolean isLoggedIn) { |
|||
SettingsFragment fragment = new SettingsFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putBoolean(ARG_IS_LOGGED_IN, isLoggedIn); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
public SettingsFragment() { |
|||
defaultHomeTabEntries.add("Recent"); |
|||
defaultHomeTabEntries.add("Forum"); |
|||
|
|||
defaultHomeTabValues.add("0"); |
|||
defaultHomeTabValues.add("1"); |
|||
|
|||
if(isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn()){ |
|||
defaultHomeTabEntries.add("Unread"); |
|||
defaultHomeTabValues.add("2"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
Bundle args = getArguments(); |
|||
|
|||
if (args != null) |
|||
isLoggedIn = args.getBoolean(ARG_IS_LOGGED_IN, false); |
|||
} |
|||
|
|||
@Override |
|||
public void onResume() { |
|||
super.onResume(); |
|||
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
super.onPause(); |
|||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); |
|||
} |
|||
|
|||
@Override |
|||
public void onCreatePreferences(Bundle bundle, String rootKey) { |
|||
isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn(); //Ensures it stays updated
|
|||
// Add the Preferences from the XML file if needed
|
|||
if(isLoggedIn&&(prefs_type==PREFS_TYPE.GUEST||prefs_type==PREFS_TYPE.NOT_SET)){ |
|||
prefs_type = PREFS_TYPE.USER; |
|||
addPreferencesFromResource(R.xml.app_preferences_user); |
|||
} |
|||
else if(!isLoggedIn&&(prefs_type==PREFS_TYPE.USER||prefs_type==PREFS_TYPE.NOT_SET)){ |
|||
prefs_type = PREFS_TYPE.GUEST; |
|||
addPreferencesFromResource(R.xml.app_preferences_guest); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { |
|||
super.onViewCreated(view, savedInstanceState); |
|||
updatePreferenceVisibility(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onPreferenceTreeClick(Preference preference) { |
|||
if (preference.getKey().equals(SELECTED_NOTIFICATIONS_SOUND)) { |
|||
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); |
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); |
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); |
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); |
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI); |
|||
|
|||
Activity activity = this.getActivity(); |
|||
settingsFile = activity != null |
|||
? activity.getSharedPreferences(SETTINGS_SHARED_PREFS, Context.MODE_PRIVATE) |
|||
: null; |
|||
String existingValue = settingsFile != null |
|||
? settingsFile.getString(SELECTED_RINGTONE, null) |
|||
: null; |
|||
if (existingValue != null) { |
|||
if (existingValue.equals(SILENT_SELECTED)) { |
|||
//Selects "Silent"
|
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (Uri) null); |
|||
} else { |
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(existingValue)); |
|||
} |
|||
} else { |
|||
//No ringtone has been selected, set to the default
|
|||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Settings.System.DEFAULT_NOTIFICATION_URI); |
|||
} |
|||
|
|||
startActivityForResult(intent, REQUEST_CODE_ALERT_RINGTONE); |
|||
return true; |
|||
} else { |
|||
return super.onPreferenceTreeClick(preference); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
|||
if (requestCode == REQUEST_CODE_ALERT_RINGTONE && data != null) { |
|||
Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); |
|||
SharedPreferences.Editor editor = settingsFile.edit(); |
|||
if (ringtone != null) { |
|||
editor.putString(SELECTED_RINGTONE, ringtone.toString()).apply(); |
|||
} else { |
|||
//"Silent" was selected
|
|||
editor.putString(SELECTED_RINGTONE, SILENT_SELECTED).apply(); |
|||
} |
|||
} else { |
|||
super.onActivityResult(requestCode, resultCode, data); |
|||
} |
|||
} |
|||
|
|||
public void updateUserLoginState(boolean isLoggedIn) { |
|||
this.isLoggedIn = isLoggedIn; |
|||
updatePreferenceVisibility(); |
|||
} |
|||
|
|||
private void updatePreferenceVisibility(){ |
|||
if(isLoggedIn&& prefs_type==PREFS_TYPE.GUEST) { |
|||
prefs_type = PREFS_TYPE.USER; |
|||
setPreferencesFromResource(R.xml.app_preferences_user, getPreferenceScreen().getKey()); |
|||
if(!defaultHomeTabEntries.contains("Unread")){ |
|||
defaultHomeTabEntries.add("Unread"); |
|||
defaultHomeTabValues.add("2"); |
|||
} |
|||
} |
|||
else if(!isLoggedIn&&prefs_type==PREFS_TYPE.USER){ |
|||
prefs_type = PREFS_TYPE.GUEST; |
|||
setPreferencesFromResource(R.xml.app_preferences_guest,getPreferenceScreen().getKey()); |
|||
if(defaultHomeTabEntries.contains("Unread")){ |
|||
defaultHomeTabEntries.remove("Unread"); |
|||
defaultHomeTabValues.remove("2"); |
|||
} |
|||
} |
|||
|
|||
CharSequence[] tmpCs = defaultHomeTabEntries.toArray(new CharSequence[defaultHomeTabEntries.size()]); |
|||
((ListPreference) findPreference(DEFAULT_HOME_TAB)).setEntries(tmpCs); |
|||
|
|||
tmpCs = defaultHomeTabValues.toArray(new CharSequence[defaultHomeTabValues.size()]); |
|||
((ListPreference) findPreference(DEFAULT_HOME_TAB)).setEntryValues(tmpCs); |
|||
} |
|||
|
|||
@Override |
|||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { |
|||
boolean enabled; |
|||
if (key.equals(getString(R.string.pref_privacy_crashlytics_enable_key))) { |
|||
enabled = sharedPreferences.getBoolean(key, false); |
|||
if(enabled) |
|||
BaseApplication.getInstance().startFirebaseCrashlyticsCollection(); |
|||
else { |
|||
Timber.i("Crashlytics collection will be disabled after restarting."); |
|||
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show(); |
|||
} |
|||
} else if (key.equals(getString(R.string.pref_privacy_analytics_enable_key))) { |
|||
enabled = sharedPreferences.getBoolean(key, false); |
|||
BaseApplication.getInstance().setFirebaseAnalyticsCollection(enabled); |
|||
if(enabled) |
|||
Timber.i("Analytics collection enabled."); |
|||
else |
|||
Timber.i("Analytics collection disabled."); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import gr.thmmy.mthmmy.utils.NetworkTask; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
public class DeleteTask extends NetworkTask<Void> { |
|||
|
|||
public DeleteTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) { |
|||
super(onTaskStartedListener, onParseTaskFinishedListener); |
|||
} |
|||
|
|||
@Override |
|||
protected Response sendRequest(OkHttpClient client, String... input) throws IOException { |
|||
Request delete = new Request.Builder() |
|||
.url(input[0]) |
|||
.header("User-Agent", |
|||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") |
|||
.build(); |
|||
client.newCall(delete).execute(); |
|||
return client.newCall(delete).execute(); |
|||
} |
|||
|
|||
@Override |
|||
protected Void performTask(Document document, Response response) { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected int getResultCode(Response response, Void data) { |
|||
return NetworkResultCodes.SUCCESSFUL; |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import okhttp3.MultipartBody; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.RequestBody; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus; |
|||
|
|||
public class EditTask extends AsyncTask<String, Void, Boolean> { |
|||
private EditTaskCallbacks listener; |
|||
private int position; |
|||
|
|||
public EditTask(EditTaskCallbacks listener, int position) { |
|||
this.listener = listener; |
|||
this.position = position; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
listener.onEditTaskStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected Boolean doInBackground(String... strings) { |
|||
RequestBody postBody = new MultipartBody.Builder() |
|||
.setType(MultipartBody.FORM) |
|||
.addFormDataPart("message", strings[1]) |
|||
.addFormDataPart("num_replies", strings[2]) |
|||
.addFormDataPart("seqnum", strings[3]) |
|||
.addFormDataPart("sc", strings[4]) |
|||
.addFormDataPart("subject", strings[5]) |
|||
.addFormDataPart("topic", strings[6]) |
|||
.build(); |
|||
Request post = new Request.Builder() |
|||
.url(strings[0]) |
|||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") |
|||
.post(postBody) |
|||
.build(); |
|||
|
|||
try { |
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
client.newCall(post).execute(); |
|||
Response response = client.newCall(post).execute(); |
|||
switch (replyStatus(response)) { |
|||
case SUCCESSFUL: |
|||
BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_editing", null); |
|||
return true; |
|||
case NEW_REPLY_WHILE_POSTING: |
|||
//TODO this...
|
|||
return true; |
|||
default: |
|||
Timber.e("Malformed post. Request string: %s", post.toString()); |
|||
return true; |
|||
} |
|||
} catch (IOException e) { |
|||
Timber.e(e, "Edit failed."); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Boolean result) { |
|||
listener.onEditTaskFinished(result, position); |
|||
} |
|||
|
|||
public interface EditTaskCallbacks { |
|||
void onEditTaskStarted(); |
|||
void onEditTaskFinished(boolean result, int position); |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
public class PrepareForEditResult { |
|||
private final String postText, commitEditUrl, numReplies, seqnum, sc, topic; |
|||
private int position; |
|||
private boolean successful; |
|||
|
|||
public PrepareForEditResult(String postText, String commitEditUrl, String numReplies, String seqnum, |
|||
String sc, String topic, int position, boolean successful) { |
|||
this.postText = postText; |
|||
this.commitEditUrl = commitEditUrl; |
|||
this.numReplies = numReplies; |
|||
this.seqnum = seqnum; |
|||
this.sc = sc; |
|||
this.topic = topic; |
|||
this.position = position; |
|||
this.successful = successful; |
|||
} |
|||
|
|||
public String getPostText() { |
|||
return postText; |
|||
} |
|||
|
|||
public String getCommitEditUrl() { |
|||
return commitEditUrl; |
|||
} |
|||
|
|||
public String getNumReplies() { |
|||
return numReplies; |
|||
} |
|||
|
|||
public String getSeqnum() { |
|||
return seqnum; |
|||
} |
|||
|
|||
public String getSc() { |
|||
return sc; |
|||
} |
|||
|
|||
public String getTopic() { |
|||
return topic; |
|||
} |
|||
|
|||
public int getPosition() { |
|||
return position; |
|||
} |
|||
|
|||
public boolean isSuccessful() { |
|||
return successful; |
|||
} |
|||
} |
@ -0,0 +1,79 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Selector; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
public class PrepareForEditTask extends AsyncTask<String, Void, PrepareForEditResult> { |
|||
private int position; |
|||
private String replyPageUrl; |
|||
private PrepareForEditCallbacks listener; |
|||
private OnPrepareEditFinished finishListener; |
|||
|
|||
public PrepareForEditTask(PrepareForEditCallbacks listener, OnPrepareEditFinished finishListener, int position, String replyPageUrl) { |
|||
this.listener = listener; |
|||
this.finishListener = finishListener; |
|||
this.position = position; |
|||
this.replyPageUrl = replyPageUrl; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
listener.onPrepareEditStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected PrepareForEditResult doInBackground(String... strings) { |
|||
Document document; |
|||
String url = strings[0]; |
|||
Request request = new Request.Builder() |
|||
.url(url + ";wap2") |
|||
.build(); |
|||
|
|||
try { |
|||
String postText, commitEditURL, numReplies, seqnum, sc, topic; |
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
|
|||
Element message = document.select("textarea").first(); |
|||
postText = message.text(); |
|||
|
|||
commitEditURL = document.select("form").first().attr("action"); |
|||
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12); |
|||
seqnum = document.select("input[name=seqnum]").first().attr("value"); |
|||
sc = document.select("input[name=sc]").first().attr("value"); |
|||
topic = document.select("input[name=topic]").first().attr("value"); |
|||
|
|||
return new PrepareForEditResult(postText, commitEditURL, numReplies, seqnum, sc, topic, position, true); |
|||
} catch (IOException | Selector.SelectorParseException e) { |
|||
Timber.e(e, "Prepare failed."); |
|||
return new PrepareForEditResult(null, null, null, null, null, null, position, false); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(PrepareForEditResult result) { |
|||
finishListener.onPrepareEditFinished(result, position); |
|||
} |
|||
|
|||
public interface PrepareForEditCallbacks { |
|||
void onPrepareEditStarted(); |
|||
void onPrepareEditCancelled(); |
|||
} |
|||
|
|||
public interface OnPrepareEditFinished { |
|||
void onPrepareEditFinished(PrepareForEditResult result, int position); |
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.select.Selector; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyResult> { |
|||
private PrepareForReplyCallbacks listener; |
|||
private OnPrepareForReplyFinished finishListener; |
|||
private String replyPageUrl; |
|||
|
|||
public PrepareForReply(PrepareForReplyCallbacks listener, OnPrepareForReplyFinished finishListener, |
|||
String replyPageUrl) { |
|||
this.listener = listener; |
|||
this.finishListener = finishListener; |
|||
this.replyPageUrl = replyPageUrl; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
listener.onPrepareForReplyStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected PrepareForReplyResult doInBackground(Integer... postIndices) { |
|||
Document document; |
|||
Request request = new Request.Builder() |
|||
.url(replyPageUrl + ";wap2") |
|||
.build(); |
|||
|
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
String numReplies, seqnum, sc, topic; |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
|
|||
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12); |
|||
seqnum = document.select("input[name=seqnum]").first().attr("value"); |
|||
sc = document.select("input[name=sc]").first().attr("value"); |
|||
topic = document.select("input[name=topic]").first().attr("value"); |
|||
} catch (IOException | Selector.SelectorParseException e) { |
|||
Timber.e(e, "Prepare failed."); |
|||
return new PrepareForReplyResult(false, null, null, null, null, null); |
|||
} |
|||
|
|||
StringBuilder buildedQuotes = new StringBuilder(""); |
|||
for (Integer postIndex : postIndices) { |
|||
request = new Request.Builder() |
|||
.url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" + |
|||
postIndex + ";" + "sesc=" + sc + ";xml") |
|||
.build(); |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
String body = response.body().string(); |
|||
buildedQuotes.append(body.substring(body.indexOf("<quote>") + 7, body.indexOf("</quote>"))); |
|||
buildedQuotes.append("\n\n"); |
|||
} catch (IOException | Selector.SelectorParseException e) { |
|||
Timber.e(e, "Quote building failed."); |
|||
return new PrepareForReplyResult(false, null, null, null, null, null); |
|||
} |
|||
} |
|||
return new PrepareForReplyResult(true, numReplies, seqnum, sc, topic, buildedQuotes.toString()); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(PrepareForReplyResult result) { |
|||
finishListener.onPrepareForReplyFinished(result); |
|||
} |
|||
|
|||
public interface PrepareForReplyCallbacks { |
|||
void onPrepareForReplyStarted(); |
|||
void onPrepareForReplyCancelled(); |
|||
} |
|||
|
|||
public interface OnPrepareForReplyFinished { |
|||
void onPrepareForReplyFinished(PrepareForReplyResult result); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
public class PrepareForReplyResult { |
|||
private final String numReplies, seqnum, sc, topic, buildedQuotes; |
|||
private boolean successful; |
|||
|
|||
|
|||
public PrepareForReplyResult(boolean successful, String numReplies, String seqnum, String sc, String topic, String buildedQuotes) { |
|||
this.successful = successful; |
|||
this.numReplies = numReplies; |
|||
this.seqnum = seqnum; |
|||
this.sc = sc; |
|||
this.topic = topic; |
|||
this.buildedQuotes = buildedQuotes; |
|||
} |
|||
|
|||
public String getNumReplies() { |
|||
return numReplies; |
|||
} |
|||
|
|||
public String getSeqnum() { |
|||
return seqnum; |
|||
} |
|||
|
|||
public String getSc() { |
|||
return sc; |
|||
} |
|||
|
|||
public String getTopic() { |
|||
return topic; |
|||
} |
|||
|
|||
public String getBuildedQuotes() { |
|||
return buildedQuotes; |
|||
} |
|||
|
|||
public boolean isSuccessful() { |
|||
return successful; |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import gr.thmmy.mthmmy.utils.NetworkTask; |
|||
import okhttp3.Response; |
|||
|
|||
public class RemoveVoteTask extends NetworkTask<Void> { |
|||
|
|||
@Override |
|||
protected Void performTask(Document document, Response response) { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected int getResultCode(Response response, Void data) { |
|||
return NetworkResultCodes.SUCCESSFUL; |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.activities.topic.Posting; |
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import okhttp3.MultipartBody; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.RequestBody; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus; |
|||
|
|||
public class ReplyTask extends AsyncTask<String, Void, Posting.REPLY_STATUS> { |
|||
private ReplyTaskCallbacks listener; |
|||
private boolean includeAppSignature; |
|||
|
|||
public ReplyTask(ReplyTaskCallbacks listener, boolean includeAppSignature) { |
|||
this.listener = listener; |
|||
this.includeAppSignature = includeAppSignature; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
listener.onReplyTaskStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected Posting.REPLY_STATUS doInBackground(String... args) { |
|||
final String sentFrommTHMMY = includeAppSignature |
|||
? "\n[right][size=7pt][i]sent from [url=https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy]mTHMMY[/url] [/i][/size][/right]" |
|||
: ""; |
|||
RequestBody postBody = new MultipartBody.Builder() |
|||
.setType(MultipartBody.FORM) |
|||
.addFormDataPart("message", args[1] + sentFrommTHMMY) |
|||
.addFormDataPart("num_replies", args[2]) |
|||
.addFormDataPart("seqnum", args[3]) |
|||
.addFormDataPart("sc", args[4]) |
|||
.addFormDataPart("subject", args[0]) |
|||
.addFormDataPart("topic", args[5]) |
|||
.build(); |
|||
Request post = new Request.Builder() |
|||
.url("https://www.thmmy.gr/smf/index.php?action=post2") |
|||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") |
|||
.post(postBody) |
|||
.build(); |
|||
|
|||
try { |
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
client.newCall(post).execute(); |
|||
Response response = client.newCall(post).execute(); |
|||
return replyStatus(response); |
|||
} catch (IOException e) { |
|||
Timber.e(e, "Post failed."); |
|||
return Posting.REPLY_STATUS.OTHER_ERROR; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Posting.REPLY_STATUS result) { |
|||
listener.onReplyTaskFinished(result); |
|||
} |
|||
|
|||
public interface ReplyTaskCallbacks { |
|||
void onReplyTaskStarted(); |
|||
void onReplyTaskFinished(Posting.REPLY_STATUS result); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import gr.thmmy.mthmmy.utils.NetworkTask; |
|||
import okhttp3.MultipartBody; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
public class SubmitVoteTask extends NetworkTask<Void> { |
|||
|
|||
private int[] votes; |
|||
|
|||
public SubmitVoteTask(int... votes) { |
|||
this.votes = votes; |
|||
} |
|||
|
|||
@Override |
|||
protected Response sendRequest(OkHttpClient client, String... input) throws IOException { |
|||
MultipartBody.Builder postBodyBuilder = new MultipartBody.Builder() |
|||
.setType(MultipartBody.FORM) |
|||
.addFormDataPart("sc", input[1]); |
|||
for (int vote : votes) { |
|||
postBodyBuilder.addFormDataPart("options[]", Integer.toString(vote)); |
|||
} |
|||
|
|||
Request voteRequest = new Request.Builder() |
|||
.url(input[0]) |
|||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") |
|||
.post(postBodyBuilder.build()) |
|||
.build(); |
|||
return client.newCall(voteRequest).execute(); |
|||
} |
|||
|
|||
@Override |
|||
protected Void performTask(Document document, Response response) { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected int getResultCode(Response response, Void data) { |
|||
return NetworkResultCodes.SUCCESSFUL; |
|||
} |
|||
} |
@ -0,0 +1,149 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.activities.topic.TopicParser; |
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import gr.thmmy.mthmmy.model.Post; |
|||
import gr.thmmy.mthmmy.model.ThmmyPage; |
|||
import gr.thmmy.mthmmy.model.TopicItem; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of its |
|||
* data. |
|||
* <p>TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String |
|||
* parameter.</p> |
|||
*/ |
|||
public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> { |
|||
private TopicTaskObserver topicTaskObserver; |
|||
private OnTopicTaskCompleted finishListener; |
|||
|
|||
public TopicTask(TopicTaskObserver topicTaskObserver, OnTopicTaskCompleted finishListener) { |
|||
this.topicTaskObserver = topicTaskObserver; |
|||
this.finishListener = finishListener; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
topicTaskObserver.onTopicTaskStarted(); |
|||
} |
|||
|
|||
@Override |
|||
protected TopicTaskResult doInBackground(String... strings) { |
|||
Document topic = null; |
|||
String newPageUrl = strings[0]; |
|||
|
|||
//Finds the index of message focus if present
|
|||
int postFocus = 0; |
|||
{ |
|||
if (newPageUrl.contains("msg")) { |
|||
String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3); |
|||
if (tmp.contains(";")) |
|||
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";"))); |
|||
else if (tmp.contains("#")) |
|||
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#"))); |
|||
} |
|||
} |
|||
|
|||
Request request = new Request.Builder() |
|||
.url(newPageUrl) |
|||
.build(); |
|||
try { |
|||
Response response = BaseApplication.getInstance().getClient().newCall(request).execute(); |
|||
topic = Jsoup.parse(response.body().string()); |
|||
|
|||
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); |
|||
|
|||
//Finds topic's tree, mods and users viewing
|
|||
String topicTreeAndMods = topic.select("div.nav").first().html(); |
|||
String topicViewers = TopicParser.parseUsersViewingThisTopic(topic, language); |
|||
|
|||
//Finds reply page url
|
|||
String replyPageUrl = null; |
|||
Element replyButton = topic.select("a:has(img[alt=Reply])").first(); |
|||
if (replyButton == null) |
|||
replyButton = topic.select("a:has(img[alt=Απάντηση])").first(); |
|||
if (replyButton != null) replyPageUrl = replyButton.attr("href"); |
|||
|
|||
//Finds topic title if missing
|
|||
String topicTitle = topic.select("td[id=top_subject]").first().text(); |
|||
if (topicTitle.contains("Topic:")) { |
|||
topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7 |
|||
, topicTitle.indexOf("(Read") - 2); |
|||
} else { |
|||
topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6 |
|||
, topicTitle.indexOf("(Αναγνώστηκε") - 2); |
|||
Timber.d("Parsed title: %s", topicTitle); |
|||
} |
|||
|
|||
//Finds current page's index
|
|||
int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language); |
|||
|
|||
//Finds number of pages
|
|||
int pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language); |
|||
|
|||
ArrayList<TopicItem> newPostsList = TopicParser.parseTopic(topic, language); |
|||
|
|||
int loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(newPageUrl)); |
|||
|
|||
//Finds the position of the focused message if present
|
|||
int focusedPostIndex = 0; |
|||
for (int i = 0; i < newPostsList.size(); ++i) { |
|||
if (newPostsList.get(i) instanceof Post && ((Post) newPostsList.get(i)).getPostIndex() == postFocus) { |
|||
focusedPostIndex = i; |
|||
break; |
|||
} |
|||
} |
|||
return new TopicTaskResult(ResultCode.SUCCESS, topicTitle, replyPageUrl, newPostsList, loadedPageTopicId, |
|||
currentPageIndex, pageCount, focusedPostIndex, topicTreeAndMods, topicViewers); |
|||
} catch (IOException e) { |
|||
return new TopicTaskResult(ResultCode.NETWORK_ERROR, null, null, null, |
|||
0, 0, 0, 0, null, null); |
|||
} catch (Exception e) { |
|||
if (isUnauthorized(topic)) { |
|||
return new TopicTaskResult(ResultCode.UNAUTHORIZED, null, null, null, |
|||
0, 0, 0, 0, null, null); |
|||
} else { |
|||
Timber.e(e, "Topic parse failed"); |
|||
return new TopicTaskResult(ResultCode.PARSING_ERROR, null, null, null, |
|||
0, 0, 0, 0, null, null); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private boolean isUnauthorized(Document document) { |
|||
return document != null && document.select("body:contains(The topic or board you" + |
|||
" are looking for appears to be either missing or off limits to you.)," + |
|||
"body:contains(Το θέμα ή πίνακας που ψάχνετε ή δεν υπάρχει ή δεν " + |
|||
"είναι προσβάσιμο από εσάς.)").size() > 0; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(TopicTaskResult topicTaskResult) { |
|||
finishListener.onTopicTaskCompleted(topicTaskResult); |
|||
} |
|||
|
|||
public enum ResultCode { |
|||
SUCCESS, NETWORK_ERROR, PARSING_ERROR, UNAUTHORIZED |
|||
} |
|||
|
|||
public interface TopicTaskObserver { |
|||
void onTopicTaskStarted(); |
|||
void onTopicTaskCancelled(); |
|||
} |
|||
|
|||
public interface OnTopicTaskCompleted { |
|||
void onTopicTaskCompleted(TopicTaskResult result); |
|||
} |
|||
} |
@ -0,0 +1,95 @@ |
|||
package gr.thmmy.mthmmy.activities.topic.tasks; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.model.TopicItem; |
|||
|
|||
public class TopicTaskResult { |
|||
private final TopicTask.ResultCode resultCode; |
|||
/** |
|||
* Holds this topic's title. At first this gets the value of the topic title that came with |
|||
* bundle and is rendered in the toolbar while parsing this topic. Later, if a different topic |
|||
* title is parsed from the html source, it gets updated. |
|||
*/ |
|||
private final String topicTitle; |
|||
/** |
|||
* This topic's reply url |
|||
*/ |
|||
private final String replyPageUrl; |
|||
private final ArrayList<TopicItem> newPostsList; |
|||
/** |
|||
* The topicId of the loaded page |
|||
*/ |
|||
private final int loadedPageTopicId; |
|||
/** |
|||
* Holds current page's index (starting from 1, not 0) |
|||
*/ |
|||
private final int currentPageIndex; |
|||
/** |
|||
* Holds the requested topic's number of pages |
|||
*/ |
|||
private final int pageCount; |
|||
/** |
|||
* The index of the post that has focus |
|||
*/ |
|||
private final int focusedPostIndex; |
|||
//Topic's info related
|
|||
private final String topicTreeAndMods; |
|||
private final String topicViewers; |
|||
|
|||
public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle, |
|||
String replyPageUrl, ArrayList<TopicItem> newPostsList, int loadedPageTopicId, |
|||
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods, |
|||
String topicViewers) { |
|||
this.resultCode = resultCode; |
|||
this.topicTitle = topicTitle; |
|||
this.replyPageUrl = replyPageUrl; |
|||
this.newPostsList = newPostsList; |
|||
this.loadedPageTopicId = loadedPageTopicId; |
|||
this.currentPageIndex = currentPageIndex; |
|||
this.pageCount = pageCount; |
|||
this.focusedPostIndex = focusedPostIndex; |
|||
this.topicTreeAndMods = topicTreeAndMods; |
|||
this.topicViewers = topicViewers; |
|||
} |
|||
|
|||
public TopicTask.ResultCode getResultCode() { |
|||
return resultCode; |
|||
} |
|||
|
|||
public String getTopicTitle() { |
|||
return topicTitle; |
|||
} |
|||
|
|||
public String getReplyPageUrl() { |
|||
return replyPageUrl; |
|||
} |
|||
|
|||
public ArrayList<TopicItem> getNewPostsList() { |
|||
return newPostsList; |
|||
} |
|||
|
|||
public int getLoadedPageTopicId() { |
|||
return loadedPageTopicId; |
|||
} |
|||
|
|||
public int getCurrentPageIndex() { |
|||
return currentPageIndex; |
|||
} |
|||
|
|||
public int getPageCount() { |
|||
return pageCount; |
|||
} |
|||
|
|||
public int getFocusedPostIndex() { |
|||
return focusedPostIndex; |
|||
} |
|||
|
|||
public String getTopicTreeAndMods() { |
|||
return topicTreeAndMods; |
|||
} |
|||
|
|||
public String getTopicViewers() { |
|||
return topicViewers; |
|||
} |
|||
} |
@ -0,0 +1,605 @@ |
|||
package gr.thmmy.mthmmy.activities.upload; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.content.pm.ResolveInfo; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.provider.MediaStore; |
|||
import android.support.design.widget.FloatingActionButton; |
|||
import android.support.v7.content.res.AppCompatResources; |
|||
import android.support.v7.preference.PreferenceManager; |
|||
import android.support.v7.widget.AppCompatButton; |
|||
import android.support.v7.widget.AppCompatTextView; |
|||
import android.view.View; |
|||
import android.widget.AdapterView; |
|||
import android.widget.ArrayAdapter; |
|||
import android.widget.EditText; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.Toast; |
|||
|
|||
import net.gotev.uploadservice.MultipartUploadRequest; |
|||
import net.gotev.uploadservice.ServerResponse; |
|||
import net.gotev.uploadservice.UploadInfo; |
|||
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.text.SimpleDateFormat; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import gr.thmmy.mthmmy.model.UploadCategory; |
|||
import gr.thmmy.mthmmy.utils.AppCompatSpinnerWithoutDefault; |
|||
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_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; |
|||
|
|||
public class UploadActivity extends BaseActivity { |
|||
/** |
|||
* The key to use when putting upload's category String to {@link UploadActivity}'s Bundle. |
|||
*/ |
|||
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 = "<br /><div style=\"text-align: right;\"><span style=\"font-style: italic;\">uploaded from <a href=\"https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy\">mTHMMY</a></span>"; |
|||
/** |
|||
* 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 ArrayList<UploadCategory> uploadRootCategories = new ArrayList<>(); |
|||
private ParseUploadPageTask parseUploadPageTask; |
|||
private ArrayList<String> bundleCategory; |
|||
private String categorySelected = "-1"; |
|||
private String uploaderProfileIndex = "1"; |
|||
private String uploadFilename; |
|||
private Uri fileUri; |
|||
private String fileIcon; |
|||
|
|||
//UI elements
|
|||
private MaterialProgressBar progressBar; |
|||
private LinearLayout categoriesSpinners; |
|||
private AppCompatSpinnerWithoutDefault rootCategorySpinner; |
|||
private EditText uploadTitle; |
|||
private EditText uploadDescription; |
|||
private AppCompatButton titleDescriptionBuilderButton; |
|||
private AppCompatTextView filenameHolder; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_upload); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
if (extras != null) { |
|||
String tmpUploadCategoryNav = extras.getString(BUNDLE_UPLOAD_CATEGORY); |
|||
//something like "THMMY.gr > Downloads > Βασικός Κύκλος > 3ο εξάμηνο > Ηλεκτρικά Κυκλώματα ΙΙ"
|
|||
if (tmpUploadCategoryNav != null && !tmpUploadCategoryNav.equals("")) { |
|||
String[] tmpSplitUploadCategoryNav = tmpUploadCategoryNav.split(">"); |
|||
|
|||
for (int i = 0; i < tmpSplitUploadCategoryNav.length; ++i) { |
|||
tmpSplitUploadCategoryNav[i] = tmpSplitUploadCategoryNav[i].trim(); |
|||
} |
|||
|
|||
if (tmpSplitUploadCategoryNav.length > 2) { |
|||
bundleCategory = new ArrayList<>(Arrays.asList(tmpSplitUploadCategoryNav).subList(2, tmpSplitUploadCategoryNav.length)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = findViewById(R.id.toolbar); |
|||
toolbar.setTitle("Upload"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(UPLOAD_ID); |
|||
|
|||
progressBar = findViewById(R.id.progressBar); |
|||
|
|||
findViewById(R.id.upload_outer_scrollview).setVerticalScrollBarEnabled(false); |
|||
categoriesSpinners = findViewById(R.id.upload_spinners); |
|||
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; |
|||
} |
|||
|
|||
int numberOfSpinners = categoriesSpinners.getChildCount(); |
|||
|
|||
if (numberOfSpinners < 3) { |
|||
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show(); |
|||
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; |
|||
} |
|||
|
|||
//Fixes course and semester
|
|||
String course = maybeCourse.replaceAll("-", "").replace("(ΝΠΣ)", "").trim(); |
|||
String semester = maybeSemester.replaceAll("-", "").trim().substring(0, 1); |
|||
|
|||
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); |
|||
}); |
|||
titleDescriptionBuilderButton.setEnabled(false); |
|||
|
|||
uploadTitle = findViewById(R.id.upload_title); |
|||
uploadDescription = findViewById(R.id.upload_description); |
|||
|
|||
filenameHolder = findViewById(R.id.upload_filename); |
|||
Drawable filenameDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_attach_file_white_24dp); |
|||
filenameHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(filenameDrawable, null, null, null); |
|||
|
|||
AppCompatButton selectFileButton = findViewById(R.id.upload_select_file_button); |
|||
Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp); |
|||
selectFileButton.setCompoundDrawablesRelativeWithIntrinsicBounds(selectStartDrawable, null, null, null); |
|||
selectFileButton.setOnClickListener(v -> { |
|||
String[] mimeTypes = {"image/jpeg", "text/html", "image/png", "image/jpg", "image/gif", |
|||
"application/pdf", "application/rar", "application/x-tar", "application/zip", |
|||
"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); |
|||
|
|||
startActivityForResult(intent, AFR_REQUEST_CODE_CHOOSE_FILE); |
|||
}); |
|||
|
|||
AppCompatButton takePhotoButton = findViewById(R.id.upload_take_photo_button); |
|||
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<ResolveInfo> resInfo = this.getPackageManager().queryIntentActivities(takePhotoIntent, 0); |
|||
for (ResolveInfo resolveInfo : resInfo) { |
|||
String packageName = resolveInfo.activityInfo.packageName; |
|||
targetedIntent.setPackage(packageName); |
|||
} |
|||
startActivityForResult(takePhotoIntent, AFR_REQUEST_CODE_CAMERA); |
|||
}); |
|||
|
|||
FloatingActionButton uploadFAB = findViewById(R.id.upload_fab); |
|||
uploadFAB.setOnClickListener(view -> { |
|||
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 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(); |
|||
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) { |
|||
} |
|||
|
|||
@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); |
|||
} |
|||
|
|||
@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); |
|||
} |
|||
|
|||
@Override |
|||
public void onCancelled(Context context, UploadInfo uploadInfo) { |
|||
Toast.makeText(context, "Upload canceled", Toast.LENGTH_SHORT).show(); |
|||
|
|||
UploadsHelper.deleteTempFiles(); |
|||
progressBar.setVisibility(View.GONE); |
|||
} |
|||
}) |
|||
.startUpload(); |
|||
} catch (Exception exception) { |
|||
Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); |
|||
progressBar.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
|
|||
if (uploadRootCategories.isEmpty()) { |
|||
//Parses the uploads page
|
|||
parseUploadPageTask = new ParseUploadPageTask(); |
|||
parseUploadPageTask.execute(uploadIndexUrl); |
|||
} else { |
|||
//Renders the already parsed data
|
|||
updateUIElements(); |
|||
titleDescriptionBuilderButton.setEnabled(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if (drawer.isDrawerOpen()) { |
|||
drawer.closeDrawer(); |
|||
return; |
|||
} |
|||
super.onBackPressed(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(UPLOAD_ID); |
|||
super.onResume(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDestroy() { |
|||
super.onDestroy(); |
|||
if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
parseUploadPageTask.cancel(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
|||
if (requestCode == AFR_REQUEST_CODE_CHOOSE_FILE) { |
|||
if (resultCode == Activity.RESULT_CANCELED || data == null) { |
|||
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"; |
|||
} |
|||
} |
|||
} else if (requestCode == AFR_REQUEST_CODE_CAMERA) { |
|||
if (resultCode == Activity.RESULT_CANCELED) { |
|||
return; |
|||
} |
|||
|
|||
Bitmap bitmap; |
|||
File cacheImageFile = UploadsHelper.getCacheFile(this); |
|||
|
|||
Uri cacheFileUri = Uri.fromFile(cacheImageFile); |
|||
fileIcon = "jpg_image.gif"; |
|||
|
|||
bitmap = UploadsHelper.getImageResized(this, cacheFileUri); |
|||
int rotation = UploadsHelper.getRotation(this, cacheFileUri); |
|||
bitmap = UploadsHelper.rotate(bitmap, rotation); |
|||
|
|||
try { |
|||
FileOutputStream out = new FileOutputStream(cacheImageFile); |
|||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); |
|||
out.flush(); |
|||
out.close(); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
|
|||
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); |
|||
} else if (requestCode == AFR_REQUEST_CODE_FIELDS_BUILDER) { |
|||
if (resultCode == Activity.RESULT_CANCELED) { |
|||
return; |
|||
} |
|||
|
|||
uploadFilename = data.getStringExtra(RESULT_FILENAME); |
|||
uploadTitle.setText(data.getStringExtra(RESULT_TITLE)); |
|||
uploadDescription.setText(data.getStringExtra(RESULT_DESCRIPTION)); |
|||
} else { |
|||
super.onActivityResult(requestCode, resultCode, data); |
|||
} |
|||
} |
|||
|
|||
private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener { |
|||
private ArrayList<UploadCategory> parentCategories, childCategories; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
private CustomOnItemSelectedListener() { |
|||
} |
|||
|
|||
CustomOnItemSelectedListener(ArrayList<UploadCategory> parentCategories) { |
|||
this.parentCategories = parentCategories; |
|||
} |
|||
|
|||
@Override |
|||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { |
|||
//Removes old, unneeded sub-category spinner(s)
|
|||
int viewIndex = categoriesSpinners.indexOfChild((AppCompatSpinnerWithoutDefault) view.getParent()); |
|||
|
|||
if (viewIndex + 1 != categoriesSpinners.getChildCount()) { //Makes sure this is not the last child
|
|||
categoriesSpinners.removeViews(viewIndex + 1, categoriesSpinners.getChildCount() - viewIndex - 1); |
|||
} |
|||
|
|||
categorySelected = parentCategories.get(position).getValue(); |
|||
|
|||
//Adds new sub-category spinner
|
|||
if (parentCategories.get(position).hasSubCategories()) { |
|||
childCategories = parentCategories.get(position).getSubCategories(); |
|||
|
|||
String[] tmpSpinnerArray = new String[childCategories.size()]; |
|||
for (int i = 0; i < tmpSpinnerArray.length; ++i) { |
|||
tmpSpinnerArray[i] = childCategories.get(i).getCategoryTitle(); |
|||
} |
|||
|
|||
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(getApplicationContext(), |
|||
R.layout.spinner_item, tmpSpinnerArray); |
|||
spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item); |
|||
|
|||
AppCompatSpinnerWithoutDefault subSpinner = new AppCompatSpinnerWithoutDefault(categoriesSpinners.getContext()); |
|||
subSpinner.setPromptId(R.string.upload_spinners_hint); |
|||
subSpinner.setPopupBackgroundResource(R.color.primary); |
|||
subSpinner.setAdapter(spinnerArrayAdapter); |
|||
subSpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(childCategories)); |
|||
categoriesSpinners.addView(subSpinner); |
|||
|
|||
//Sets bundle selection
|
|||
if (bundleCategory != null && !bundleCategory.isEmpty()) { |
|||
int bundleSelectionIndex = -1, currentIndex = 0; |
|||
|
|||
for (UploadCategory category : childCategories) { |
|||
if (bundleCategory.get(0).contains(category.getCategoryTitle() |
|||
.replace("-", "").trim())) { |
|||
bundleSelectionIndex = currentIndex; |
|||
break; |
|||
} |
|||
++currentIndex; |
|||
} |
|||
|
|||
if (bundleSelectionIndex != -1) { |
|||
subSpinner.setSelection(bundleSelectionIndex, true); |
|||
bundleCategory.remove(0); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onNothingSelected(AdapterView<?> parent) { |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* An {@link ParseTask} that handles asynchronous fetching of the upload page and parsing the |
|||
* upload categories. |
|||
*/ |
|||
private class ParseUploadPageTask extends ParseTask { |
|||
@Override |
|||
protected void onPreExecute() { |
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
protected void parse(Document uploadPage) throws ParseException { |
|||
Elements categoriesElements; |
|||
Element uploaderProfileIndexElement; |
|||
|
|||
try { |
|||
categoriesElements = uploadPage.select("select[name='tp-dluploadcat']>option"); |
|||
uploaderProfileIndexElement = uploadPage.select("input[name=\"tp-uploaduser\"]").first(); |
|||
} catch (Exception e) { |
|||
throw new ParseException("Parsing failed (UploadActivity)"); |
|||
} |
|||
|
|||
uploaderProfileIndex = uploaderProfileIndexElement.attr("value"); |
|||
|
|||
for (Element category : categoriesElements) { |
|||
String categoryValue = category.attr("value"); |
|||
String categoryText = category.text(); |
|||
|
|||
if (categoryText.startsWith("- ")) { |
|||
//This is a level one subcategory
|
|||
uploadRootCategories.get(uploadRootCategories.size() - 1).addSubCategory(categoryValue, categoryText); |
|||
} else if (categoryText.startsWith("-- ")) { |
|||
//This is a level two subcategory
|
|||
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1); |
|||
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1); |
|||
firstLevelCategory.addSubCategory(categoryValue, categoryText); |
|||
} else if (categoryText.startsWith("--- ")) { |
|||
//This is a level three subcategory
|
|||
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1); |
|||
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1); |
|||
UploadCategory secondLevelCategory = firstLevelCategory.getSubCategories().get(firstLevelCategory.getSubCategories().size() - 1); |
|||
secondLevelCategory.addSubCategory(categoryValue, categoryText); |
|||
} else if (categoryText.startsWith("---- ")) { |
|||
//This is a level four subcategory
|
|||
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1); |
|||
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1); |
|||
UploadCategory secondLevelCategory = firstLevelCategory.getSubCategories().get(firstLevelCategory.getSubCategories().size() - 1); |
|||
UploadCategory thirdLevelCategory = secondLevelCategory.getSubCategories().get(secondLevelCategory.getSubCategories().size() - 1); |
|||
thirdLevelCategory.addSubCategory(categoryValue, categoryText); |
|||
} else { |
|||
//This is a root category
|
|||
uploadRootCategories.add(new UploadCategory(categoryValue, categoryText)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@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(); |
|||
} |
|||
|
|||
ArrayAdapter<String> 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); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,516 @@ |
|||
package gr.thmmy.mthmmy.activities.upload; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Intent; |
|||
import android.os.Bundle; |
|||
import android.support.annotation.Nullable; |
|||
import android.support.v7.app.AppCompatActivity; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.text.Editable; |
|||
import android.text.TextWatcher; |
|||
import android.view.View; |
|||
import android.widget.EditText; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.RadioGroup; |
|||
import android.widget.Toast; |
|||
|
|||
import java.util.Calendar; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import timber.log.Timber; |
|||
|
|||
public class UploadFieldsBuilderActivity extends AppCompatActivity { |
|||
static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE = "UPLOAD_FIELD_BUILDER_COURSE"; |
|||
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 LinearLayout semesterChooserLinear; |
|||
private RadioGroup typeRadio, semesterRadio; |
|||
private EditText year; |
|||
|
|||
private TextWatcher customYearWatcher = new TextWatcher() { |
|||
|
|||
@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; |
|||
} |
|||
|
|||
if (!isValid) { |
|||
year.setError("Please enter a valid year"); |
|||
} else { |
|||
year.setError(null); |
|||
} |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void afterTextChanged(Editable s) { |
|||
} |
|||
|
|||
@Override |
|||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
|||
} |
|||
}; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_upload_fields_builder); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
if (extras != null) { |
|||
course = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE); |
|||
semester = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER); |
|||
if (course == null || course.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()); |
|||
|
|||
Intent returnIntent = new Intent(); |
|||
setResult(Activity.RESULT_CANCELED, returnIntent); |
|||
finish(); |
|||
} |
|||
} |
|||
|
|||
//Initialize toolbar
|
|||
Toolbar toolbar = findViewById(R.id.toolbar); |
|||
toolbar.setTitle(R.string.upload_fields_builder_toolbar_title); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
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 |
|||
? R.id.upload_fields_builder_radio_button_jun |
|||
: R.id.upload_fields_builder_radio_button_feb); |
|||
|
|||
year = findViewById(R.id.upload_fields_builder_year); |
|||
year.addTextChangedListener(customYearWatcher); |
|||
|
|||
typeRadio = findViewById(R.id.upload_fields_builder_type_radio_group); |
|||
typeRadio.setOnCheckedChangeListener((group, checkedId) -> { |
|||
if (checkedId == R.id.upload_fields_builder_radio_button_notes) { |
|||
semesterChooserLinear.setVisibility(View.GONE); |
|||
} else { |
|||
semesterChooserLinear.setVisibility(View.VISIBLE); |
|||
} |
|||
}); |
|||
|
|||
findViewById(R.id.upload_fields_builder_submit).setOnClickListener(view -> { |
|||
int typeId = typeRadio.getCheckedRadioButtonId(), |
|||
semesterId = semesterRadio.getCheckedRadioButtonId(); |
|||
if (typeId == -1) { |
|||
Toast.makeText(view.getContext(), "Please choose a type for the upload", Toast.LENGTH_SHORT).show(); |
|||
return; |
|||
} 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(); |
|||
return; |
|||
} |
|||
|
|||
Intent returnIntent = new Intent(); |
|||
returnIntent.putExtra(RESULT_FILENAME, buildFilename()); |
|||
returnIntent.putExtra(RESULT_TITLE, buildTitle()); |
|||
returnIntent.putExtra(RESULT_DESCRIPTION, buildDescription()); |
|||
setResult(Activity.RESULT_OK, returnIntent); |
|||
finish(); |
|||
}); |
|||
} |
|||
|
|||
@Nullable |
|||
private String buildFilename() { |
|||
switch (typeRadio.getCheckedRadioButtonId()) { |
|||
case R.id.upload_fields_builder_radio_button_exams: |
|||
return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString(); |
|||
case R.id.upload_fields_builder_radio_button_exam_solutions: |
|||
return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString() + "_Lyseis"; |
|||
case R.id.upload_fields_builder_radio_button_notes: |
|||
return getGreeklishCourseName() + "_" + year.getText().toString() + "_Shmeiwseis"; |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@Nullable |
|||
private String buildTitle() { |
|||
switch (typeRadio.getCheckedRadioButtonId()) { |
|||
case R.id.upload_fields_builder_radio_button_exams: |
|||
return getMinifiedCourseName() + " - " + "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString(); |
|||
case R.id.upload_fields_builder_radio_button_exam_solutions: |
|||
return getMinifiedCourseName() + " - " + "Λύσεις θεμάτων " + getPeriod() + " " + year.getText().toString(); |
|||
case R.id.upload_fields_builder_radio_button_notes: |
|||
return getMinifiedCourseName() + " - " + "Σημειώσεις παραδόσεων " + year.getText().toString(); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private String buildDescription() { |
|||
switch (typeRadio.getCheckedRadioButtonId()) { |
|||
case R.id.upload_fields_builder_radio_button_exams: |
|||
return "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\""; |
|||
case R.id.upload_fields_builder_radio_button_exam_solutions: |
|||
return "Λύσεις των θεμάτων των εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\""; |
|||
case R.id.upload_fields_builder_radio_button_notes: |
|||
return "Σημειώσεις των παραδόσεων του μαθήματος \"" + course + "\" από το " + year.getText().toString(); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private String getGreeklishPeriod() { |
|||
switch (semesterRadio.getCheckedRadioButtonId()) { |
|||
case R.id.upload_fields_builder_radio_button_feb: |
|||
return "FEB"; |
|||
case R.id.upload_fields_builder_radio_button_jun: |
|||
return "IOY"; |
|||
case R.id.upload_fields_builder_radio_button_sept: |
|||
return "SEP"; |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private String getPeriod() { |
|||
switch (semesterRadio.getCheckedRadioButtonId()) { |
|||
case R.id.upload_fields_builder_radio_button_feb: |
|||
return "Φεβρουαρίου"; |
|||
case R.id.upload_fields_builder_radio_button_jun: |
|||
return "Ιουνίου"; |
|||
case R.id.upload_fields_builder_radio_button_sept: |
|||
return "Σεπτεμβρίου"; |
|||
default: |
|||
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; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,199 @@ |
|||
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.support.annotation.NonNull; |
|||
import android.support.annotation.Nullable; |
|||
import android.widget.Toast; |
|||
|
|||
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 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; |
|||
} |
|||
|
|||
@SuppressWarnings("ResultOfMethodCallIgnored") |
|||
@Nullable |
|||
static String createTempFile(Context context, Uri fileUri, String newFilename) { |
|||
String oldFilename = filenameFromUri(context, fileUri); |
|||
String fileExtension = oldFilename.substring(oldFilename.indexOf(".")); |
|||
String destinationFilename = Environment.getExternalStorageDirectory().getPath() + |
|||
File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension; |
|||
|
|||
File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() + |
|||
File.separatorChar + "~tmp_mThmmy_uploads"); |
|||
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
InputStream inputStream; |
|||
BufferedInputStream bufferedInputStream = null; |
|||
BufferedOutputStream bufferedOutputStream = null; |
|||
|
|||
try { |
|||
inputStream = context.getContentResolver().openInputStream(fileUri); |
|||
if (inputStream == null) { |
|||
Timber.w("Input stream was null, %s", UploadActivity.class.getSimpleName()); |
|||
return null; |
|||
} |
|||
|
|||
bufferedInputStream = new BufferedInputStream(inputStream); |
|||
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destinationFilename, false)); |
|||
byte[] buf = new byte[1024]; |
|||
bufferedInputStream.read(buf); |
|||
do { |
|||
bufferedOutputStream.write(buf); |
|||
} while (bufferedInputStream.read(buf) != -1); |
|||
} catch (IOException exception) { |
|||
exception.printStackTrace(); |
|||
} finally { |
|||
try { |
|||
if (bufferedInputStream != null) bufferedInputStream.close(); |
|||
if (bufferedOutputStream != null) bufferedOutputStream.close(); |
|||
} catch (IOException exception) { |
|||
exception.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
return 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; |
|||
|
|||
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); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
@ -0,0 +1,345 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.app.Activity; |
|||
import android.app.AlertDialog; |
|||
import android.content.Context; |
|||
import android.content.res.TypedArray; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.support.annotation.Nullable; |
|||
import android.support.design.widget.TextInputEditText; |
|||
import android.support.design.widget.TextInputLayout; |
|||
import android.support.v7.widget.AppCompatImageButton; |
|||
import android.support.v7.widget.GridLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.text.Editable; |
|||
import android.text.TextUtils; |
|||
import android.util.AttributeSet; |
|||
import android.util.DisplayMetrics; |
|||
import android.util.SparseArray; |
|||
import android.view.LayoutInflater; |
|||
import android.view.inputmethod.EditorInfo; |
|||
import android.view.inputmethod.InputConnection; |
|||
import android.view.inputmethod.InputMethodManager; |
|||
import android.widget.Button; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.PopupWindow; |
|||
import android.widget.ScrollView; |
|||
import android.widget.TextView; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
|
|||
public class EditorView extends LinearLayout implements EmojiInputField { |
|||
|
|||
private SparseArray<String> colors = new SparseArray<>(); |
|||
|
|||
private TextInputLayout edittextWrapper; |
|||
private TextInputEditText editText; |
|||
private AppCompatImageButton emojiButton; |
|||
private AppCompatImageButton submitButton; |
|||
private IEmojiKeyboard emojiKeyboard; |
|||
|
|||
public EditorView(Context context) { |
|||
super(context); |
|||
init(context, null); |
|||
} |
|||
|
|||
public EditorView(Context context, AttributeSet attrs) { |
|||
super(context, attrs); |
|||
init(context, attrs); |
|||
} |
|||
|
|||
public EditorView(Context context, AttributeSet attrs, int defStyleAttrs) { |
|||
super(context, attrs, defStyleAttrs); |
|||
init(context, attrs); |
|||
} |
|||
|
|||
@SuppressLint("SetTextI18n") |
|||
private void init(Context context, AttributeSet attrs) { |
|||
LayoutInflater.from(context).inflate(R.layout.editor_view, this, true); |
|||
setOrientation(VERTICAL); |
|||
|
|||
edittextWrapper = findViewById(R.id.editor_edittext_wrapper); |
|||
editText = findViewById(R.id.editor_edittext); |
|||
editText.setOnFocusChangeListener((view, focused) -> { |
|||
if (focused) emojiKeyboard.onEmojiInputFieldFocused(EditorView.this); |
|||
}); |
|||
edittextWrapper.setOnFocusChangeListener((view, focused) -> { |
|||
if (focused) emojiKeyboard.onEmojiInputFieldFocused(EditorView.this); |
|||
}); |
|||
editText.setOnClickListener(view -> { |
|||
if (!emojiKeyboard.isVisible()) { |
|||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); |
|||
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); |
|||
} else { |
|||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); |
|||
imm.hideSoftInputFromWindow(getWindowToken(), 0); |
|||
requestEditTextFocus(); |
|||
} |
|||
}); |
|||
|
|||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0); |
|||
try { |
|||
editText.setHint(a.getString(R.styleable.EditorView_hint)); |
|||
} finally { |
|||
a.recycle(); |
|||
} |
|||
|
|||
// without this, the editor gets default window background
|
|||
Drawable background = getBackground(); |
|||
for (int i = 0; i < getChildCount(); i++) { |
|||
getChildAt(i).setBackground(background); |
|||
} |
|||
|
|||
emojiButton = findViewById(R.id.emoji_keyboard_button); |
|||
|
|||
colors.append(R.id.black, "black"); |
|||
colors.append(R.id.red, "red"); |
|||
colors.append(R.id.yellow, "yellow"); |
|||
colors.append(R.id.pink, "pink"); |
|||
colors.append(R.id.green, "green"); |
|||
colors.append(R.id.orange, "orange"); |
|||
colors.append(R.id.purple, "purple"); |
|||
colors.append(R.id.blue, "blue"); |
|||
colors.append(R.id.beige, "beige"); |
|||
colors.append(R.id.brown, "brown"); |
|||
colors.append(R.id.teal, "teal"); |
|||
colors.append(R.id.navy, "navy"); |
|||
colors.append(R.id.maroon, "maroon"); |
|||
colors.append(R.id.lime_green, "limegreen"); |
|||
|
|||
RecyclerView formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview); |
|||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
|||
float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) + |
|||
getResources().getDimension(R.dimen.editor_format_button_margin_between); |
|||
int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth); |
|||
formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns)); |
|||
formatButtonsRecyclerview.setAdapter(new FormatButtonsAdapter((view, drawableId) -> { |
|||
switch (drawableId) { |
|||
case R.drawable.ic_format_bold: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[b]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/b]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_italic: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[i]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/i]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_underlined: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[u]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/u]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); |
|||
break; |
|||
} |
|||
case R.drawable.ic_strikethrough_s: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[s]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/s]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_color_text: { |
|||
PopupWindow popupWindow = new PopupWindow(view.getContext()); |
|||
popupWindow.setHeight(LayoutParams.WRAP_CONTENT); |
|||
popupWindow.setWidth(LayoutParams.WRAP_CONTENT); |
|||
popupWindow.setFocusable(true); |
|||
ScrollView colorPickerScrollview = (ScrollView) LayoutInflater.from(context).inflate(R.layout.editor_view_color_picker, null); |
|||
LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0); |
|||
popupWindow.setContentView(colorPickerScrollview); |
|||
for (int i = 0; i < colorPicker.getChildCount(); i++) { |
|||
TextView child = (TextView) colorPicker.getChildAt(i); |
|||
child.setOnClickListener(v -> { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[color=" + colors.get(v.getId()) + "]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/color]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); |
|||
popupWindow.dismiss(); |
|||
}); |
|||
} |
|||
popupWindow.showAsDropDown(view); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_size: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[size=10pt]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/size]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); |
|||
break; |
|||
} |
|||
case R.drawable.ic_text_format: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[font=Verdana]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/font]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_list_bulleted: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[list]\n[li]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() - 13 : editText.getSelectionStart() - 23); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_align_left: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[left]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/left]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_align_center: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[center]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/center]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 9); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_align_right: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[right]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/right]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); |
|||
break; |
|||
} |
|||
case R.drawable.ic_insert_link: { |
|||
LinearLayout dialogBody = (LinearLayout) LayoutInflater.from(context) |
|||
.inflate(R.layout.dialog_create_link, null); |
|||
TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input); |
|||
linkUrl.setOnClickListener(view1 -> linkUrl.setError(null)); |
|||
TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input); |
|||
linkText.setOnClickListener(view2 -> linkText.setError(null)); |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
int start = editText.getSelectionStart(), end = editText.getSelectionEnd(); |
|||
if (editText.hasSelection()) { |
|||
linkText.getEditText().setText( |
|||
editText.getText().toString().substring(editText.getSelectionStart(), editText.getSelectionEnd())); |
|||
} |
|||
AlertDialog linkDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog) |
|||
.setTitle(R.string.dialog_create_link_title) |
|||
.setView(dialogBody) |
|||
.setPositiveButton(R.string.ok, null) |
|||
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) |
|||
.create(); |
|||
linkDialog.setOnShowListener(dialogInterface -> { |
|||
Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE); |
|||
button.setOnClickListener(view12 -> { |
|||
if (TextUtils.isEmpty(Objects.requireNonNull(linkUrl.getEditText()).getText().toString())) { |
|||
linkUrl.setError(context.getString(R.string.input_field_required)); |
|||
return; |
|||
} |
|||
|
|||
if (hadTextSelection) editText.getText().delete(start, end); |
|||
if (!TextUtils.isEmpty(linkText.getEditText().getText())) { |
|||
getText().insert(editText.getSelectionStart(), "[url=" + |
|||
linkUrl.getEditText().getText().toString() + "]" + |
|||
linkText.getEditText().getText().toString() + "[/url]"); |
|||
} |
|||
else |
|||
getText().insert(editText.getSelectionStart(), "[url]" + |
|||
linkUrl.getEditText().getText().toString() + "[/url]"); |
|||
linkDialog.dismiss(); |
|||
}); |
|||
}); |
|||
linkDialog.show(); |
|||
break; |
|||
} |
|||
case R.drawable.ic_format_quote: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[quote]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/quote]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); |
|||
break; |
|||
} |
|||
case R.drawable.ic_code: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[code]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/code]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); |
|||
break; |
|||
} |
|||
case R.drawable.ic_functions: { |
|||
boolean hadTextSelection = editText.hasSelection(); |
|||
getText().insert(editText.getSelectionStart(), "[tex]"); |
|||
getText().insert(editText.getSelectionEnd(), "[/tex]"); |
|||
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 6); |
|||
break; |
|||
} |
|||
default: throw new IllegalArgumentException("Unknown format button click"); |
|||
} |
|||
})); |
|||
|
|||
emojiButton.setOnClickListener(view -> { |
|||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); |
|||
//cache selection. For some reason it gets reset sometimes
|
|||
int selectionStart = editText.getSelectionStart(); |
|||
int selectionEnd = editText.getSelectionStart(); |
|||
if (emojiKeyboard.onEmojiButtonToggle()) { |
|||
//prevent system keyboard from appearing when clicking the edittext
|
|||
editText.setTextIsSelectable(true); |
|||
imm.hideSoftInputFromWindow(getWindowToken(), 0); |
|||
} |
|||
else { |
|||
editText.requestFocus(); |
|||
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); |
|||
} |
|||
editText.setSelection(selectionStart, selectionEnd); |
|||
}); |
|||
|
|||
submitButton = findViewById(R.id.submit_button); |
|||
} |
|||
|
|||
public void setEmojiKeyboard(IEmojiKeyboard emojiKeyboard) { |
|||
this.emojiKeyboard = emojiKeyboard; |
|||
} |
|||
|
|||
public TextInputEditText getEditText() { |
|||
return editText; |
|||
} |
|||
|
|||
public Editable getText() { |
|||
return editText.getText(); |
|||
} |
|||
|
|||
public void setText(Editable text) { |
|||
editText.setText(text); |
|||
} |
|||
|
|||
public void setText(CharSequence text) { |
|||
editText.setText(text); |
|||
} |
|||
|
|||
public void setError(@Nullable CharSequence text) { |
|||
edittextWrapper.setError(text); |
|||
} |
|||
|
|||
public void setOnSubmitListener(OnClickListener onSubmitListener) { |
|||
submitButton.setOnClickListener(onSubmitListener); |
|||
} |
|||
|
|||
public boolean requestEditTextFocus() { |
|||
emojiKeyboard.onEmojiInputFieldFocused(EditorView.this); |
|||
return editText.requestFocus(); |
|||
} |
|||
|
|||
@Override |
|||
public void onKeyboardVisibilityChange(boolean visible) { |
|||
if (visible) { |
|||
emojiButton.setImageResource(R.drawable.ic_keyboard_24dp); |
|||
} else { |
|||
emojiButton.setImageResource(R.drawable.ic_tag_faces_24dp); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public InputConnection getInputConnection() { |
|||
return editText.onCreateInputConnection(new EditorInfo()); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
import android.view.inputmethod.InputConnection; |
|||
|
|||
public interface EmojiInputField { |
|||
void onKeyboardVisibilityChange(boolean visible); |
|||
InputConnection getInputConnection(); |
|||
} |
@ -0,0 +1,276 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Handler; |
|||
import android.support.v7.widget.AppCompatImageButton; |
|||
import android.support.v7.widget.GridLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.text.TextUtils; |
|||
import android.util.AttributeSet; |
|||
import android.view.LayoutInflater; |
|||
import android.view.MotionEvent; |
|||
import android.view.inputmethod.InputConnection; |
|||
import android.widget.LinearLayout; |
|||
|
|||
import java.util.HashSet; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
|
|||
public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard { |
|||
|
|||
// TODO: Sort emojis in a way that makes sense
|
|||
private final Emoji[] emojis = {new Emoji(R.drawable.emoji_smiley, ":)"), |
|||
new Emoji(R.drawable.emoji_wink, ";)"), |
|||
new Emoji(R.drawable.emoji_cheesy, ":D"), |
|||
new Emoji(R.drawable.emoji_grin, ";D"), |
|||
// removed repeated angry emoji
|
|||
new Emoji(R.drawable.emoji_angry, ">:("), |
|||
new Emoji(R.drawable.emoji_sad, ":("), |
|||
new Emoji(R.drawable.emoji_shocked, ":o"), |
|||
new Emoji(R.drawable.emoji_cool, "8))"), |
|||
new Emoji(R.drawable.emoji_huh, ":???:"), |
|||
new Emoji(R.drawable.emoji_rolleyes, "::)"), |
|||
new Emoji(R.drawable.emoji_tongue, ":P"), |
|||
new Emoji(R.drawable.emoji_embarrassed, ":-["), |
|||
new Emoji(R.drawable.emoji_lipsrsealed, ":-X"), |
|||
new Emoji(R.drawable.emoji_undecided, ":-\\\\"), |
|||
new Emoji(R.drawable.emoji_kiss, ":-*"), |
|||
new Emoji(R.drawable.emoji_cry, ":'("), |
|||
new Emoji(R.drawable.emoji_heart, "<3"), |
|||
// removed repeated lock emoji
|
|||
new Emoji(R.drawable.emoji_locked, "^lock^"), |
|||
new Emoji(R.drawable.emoji_roll_over, "^rollover^"), |
|||
new Emoji(R.drawable.emoji_redface, "^redface^"), |
|||
new Emoji(R.drawable.emoji_confused, "^confused^"), |
|||
new Emoji(R.drawable.emoji_innocent, "^innocent^"), |
|||
new Emoji(R.drawable.emoji_sleep, "^sleep^"), |
|||
new Emoji(R.drawable.emoji_lips_sealed, "^sealed^"), |
|||
new Emoji(R.drawable.emoji_cool2, "^cool^"), |
|||
new Emoji(R.drawable.emoji_monster, "^monster^"), |
|||
new Emoji(R.drawable.emoji_crazy, "^crazy^"), |
|||
new Emoji(R.drawable.emoji_mad, "^mad^"), |
|||
new Emoji(R.drawable.emoji_wav, "^wav^"), |
|||
new Emoji(R.drawable.emoji_binkybaby, "^binkybaby^"), |
|||
new Emoji(R.drawable.emoji_police, "^police^"), |
|||
new Emoji(R.drawable.emoji_dontknow, "^dontknow^"), |
|||
//removed repeated angry hot emoji
|
|||
new Emoji(R.drawable.emoji_angry_hot, "^angryhot^"), |
|||
new Emoji(R.drawable.emoji_foyska, "^fouska^"), |
|||
new Emoji(R.drawable.emoji_e10_7_3e, "^sfinaki^"), |
|||
new Emoji(R.drawable.emoji_bang_head, "^banghead^"), |
|||
new Emoji(R.drawable.emoji_crybaby, "^crybaby^"), |
|||
new Emoji(R.drawable.emoji_hello, "^hello^"), |
|||
new Emoji(R.drawable.emoji_jerk, "^jerk^"), |
|||
new Emoji(R.drawable.emoji_nono, "^nono^"), |
|||
new Emoji(R.drawable.emoji_notworthy, "^notworthy^"), |
|||
new Emoji(R.drawable.emoji_off_topic, "^off-topic^"), |
|||
new Emoji(R.drawable.emoji_puke, "^puke^"), |
|||
new Emoji(R.drawable.emoji_shout, "^shout^"), |
|||
new Emoji(R.drawable.emoji_slurp, "^slurp^"), |
|||
new Emoji(R.drawable.emoji_superconfused, "^superconfused^"), |
|||
new Emoji(R.drawable.emoji_superinnocent, "^superinnocent^"), |
|||
new Emoji(R.drawable.emoji_cell_phone, "^cellPhone^"), |
|||
new Emoji(R.drawable.emoji_idiot, "^idiot^"), |
|||
new Emoji(R.drawable.emoji_knuppel, "^knuppel^"), |
|||
new Emoji(R.drawable.emoji_tickedoff, "^tickedOff^"), |
|||
new Emoji(R.drawable.emoji_peace, "^peace^"), |
|||
new Emoji(R.drawable.emoji_suspicious, "^suspicious^"), |
|||
new Emoji(R.drawable.emoji_caffine, "^caffine^"), |
|||
new Emoji(R.drawable.emoji_argue, "^argue^"), |
|||
new Emoji(R.drawable.emoji_banned2, "^banned2^"), |
|||
new Emoji(R.drawable.emoji_banned, "^banned^"), |
|||
new Emoji(R.drawable.emoji_bath, "^bath^"), |
|||
new Emoji(R.drawable.emoji_beg, "^beg^"), |
|||
new Emoji(R.drawable.emoji_bluescreen, "^bluescreen^"), |
|||
new Emoji(R.drawable.emoji_boil, "^boil^"), |
|||
new Emoji(R.drawable.emoji_bye, "^bye^"), |
|||
new Emoji(R.drawable.emoji_callmerip, "^callmerip^"), |
|||
new Emoji(R.drawable.emoji_carnaval, "^carnaval^"), |
|||
new Emoji(R.drawable.emoji_clap, "^clap^"), |
|||
new Emoji(R.drawable.emoji_coffeepot, "^coffepot^"), |
|||
new Emoji(R.drawable.emoji_crap, "^crap^"), |
|||
new Emoji(R.drawable.emoji_curses, "^curses^"), |
|||
new Emoji(R.drawable.emoji_funny, "^funny^"), |
|||
new Emoji(R.drawable.emoji_guitar1, "^guitar^"), |
|||
new Emoji(R.drawable.emoji_icon_kissy, "^kissy^"), |
|||
new Emoji(R.drawable.emoji_band, "^band^"), |
|||
new Emoji(R.drawable.emoji_ivres, "^ivres^"), |
|||
new Emoji(R.drawable.emoji_kaloe, "^kaloe^"), |
|||
new Emoji(R.drawable.emoji_kremala, "^kremala^"), |
|||
new Emoji(R.drawable.emoji_moon, "^moon^"), |
|||
new Emoji(R.drawable.emoji_mopping, "^mopping^"), |
|||
new Emoji(R.drawable.emoji_mountza, "^mountza^"), |
|||
new Emoji(R.drawable.emoji_pcsleep, "^pcsleep^"), |
|||
new Emoji(R.drawable.emoji_pinokio, "^pinokio^"), |
|||
new Emoji(R.drawable.emoji_poke, "^poke^"), |
|||
new Emoji(R.drawable.emoji_seestars, "^seestars^"), |
|||
new Emoji(R.drawable.emoji_sfyri, "^sfyri^"), |
|||
new Emoji(R.drawable.emoji_spam2, "^spam^"), |
|||
new Emoji(R.drawable.emoji_esuper, "^super^"), |
|||
new Emoji(R.drawable.emoji_tafos, "^tafos^"), |
|||
new Emoji(R.drawable.emoji_tomatomourh, "^tomato^"), |
|||
new Emoji(R.drawable.emoji_ytold, "^ytold^"), |
|||
new Emoji(R.drawable.emoji_beer2, "^beer^"), |
|||
new Emoji(R.drawable.emoji_yu, "^yue^"), |
|||
new Emoji(R.drawable.emoji_a_eatpaper, "^eatpaper^"), |
|||
new Emoji(R.drawable.emoji_fritz, "^fritz^"), |
|||
new Emoji(R.drawable.emoji_wade, "^wade^"), |
|||
new Emoji(R.drawable.emoji_lypi, "^lypi^"), |
|||
new Emoji(R.drawable.emoji_megashok1wq, "^aytoxeir^"), |
|||
new Emoji(R.drawable.emoji_victory, "^victory^"), |
|||
new Emoji(R.drawable.emoji_filarakia, "^filarakia^"), |
|||
new Emoji(R.drawable.emoji_bonjour_97213, "^hat^"), |
|||
new Emoji(R.drawable.emoji_curtseyqi9, "^miss^"), |
|||
new Emoji(R.drawable.emoji_rofl, "^rolfmao^"), |
|||
new Emoji(R.drawable.emoji_question, "^que^"), |
|||
new Emoji(R.drawable.emoji_shifty, "^shifty^"), |
|||
new Emoji(R.drawable.emoji_shy, "^shy^"), |
|||
new Emoji(R.drawable.emoji_music, "^music_listen^"), |
|||
new Emoji(R.drawable.emoji_shamed_bag, "^bagface^"), |
|||
new Emoji(R.drawable.emoji_rotfl, "^rotate^"), |
|||
new Emoji(R.drawable.emoji_love, "^love^"), |
|||
new Emoji(R.drawable.emoji_speech, "^speech^"), |
|||
new Emoji(R.drawable.emoji_facepalm, "^facepalm^"), |
|||
new Emoji(R.drawable.emoji_shocked2, "^shocked^"), |
|||
new Emoji(R.drawable.emoji_extremely_shocked, "^ex_shocked^"), |
|||
new Emoji(R.drawable.emoji_smurf, "^smurf^") |
|||
}; |
|||
|
|||
private InputConnection inputConnection; |
|||
private HashSet<EmojiInputField> emojiInputFields = new HashSet<>(); |
|||
private Context context; |
|||
|
|||
public EmojiKeyboard(Context context) { |
|||
this(context, null, 0); |
|||
} |
|||
|
|||
public EmojiKeyboard(Context context, AttributeSet attrs) { |
|||
this(context, attrs, 0); |
|||
} |
|||
|
|||
public EmojiKeyboard(Context context, AttributeSet attrs, int defStyleAttrs) { |
|||
super(context, attrs, defStyleAttrs); |
|||
init(context, attrs); |
|||
} |
|||
|
|||
public void init(Context context, AttributeSet attrs) { |
|||
this.context = context; |
|||
LayoutInflater.from(context).inflate(R.layout.emoji_keyboard, this, true); |
|||
setOrientation(VERTICAL); |
|||
setBackgroundColor(getResources().getColor(R.color.primary)); |
|||
|
|||
RecyclerView emojiRecyclerview = findViewById(R.id.emoji_recyclerview); |
|||
emojiRecyclerview.setHasFixedSize(true); |
|||
GridLayoutManager emojiLayoutManager = new GridLayoutManager(context, 6); |
|||
emojiLayoutManager.setSpanSizeLookup(new EmojiColumnSpanLookup()); |
|||
emojiRecyclerview.setLayoutManager(emojiLayoutManager); |
|||
|
|||
EmojiKeyboardAdapter emojiKeyboardAdapter = new EmojiKeyboardAdapter(emojis); |
|||
emojiKeyboardAdapter.setOnEmojiClickListener((view, position) -> { |
|||
if (inputConnection == null) return; |
|||
String bbcode = emojis[position].getBbcode(); |
|||
inputConnection.commitText(" " + bbcode, 1); |
|||
}); |
|||
emojiRecyclerview.setAdapter(emojiKeyboardAdapter); |
|||
AppCompatImageButton backspaceButton = findViewById(R.id.backspace_button); |
|||
// backspace behavior
|
|||
final Handler handler = new Handler(); |
|||
Runnable longPressed = new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
inputConnection.deleteSurroundingText(1, 0); |
|||
handler.postDelayed(this, 50); |
|||
} |
|||
}; |
|||
backspaceButton.setOnTouchListener((v, event) -> { |
|||
switch (event.getAction()) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
CharSequence selectedText = inputConnection.getSelectedText(0); |
|||
if (TextUtils.isEmpty(selectedText)) |
|||
inputConnection.deleteSurroundingText(1, 0); |
|||
else |
|||
inputConnection.commitText("", 1); |
|||
handler.postDelayed(longPressed, 400); |
|||
break; |
|||
case MotionEvent.ACTION_UP: |
|||
handler.removeCallbacks(longPressed); |
|||
break; |
|||
} |
|||
return true; |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void hide() { |
|||
setVisibility(GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void registerEmojiInputField(EmojiInputField emojiInputField) { |
|||
emojiInputFields.add(emojiInputField); |
|||
} |
|||
|
|||
public void setInputConnection(InputConnection inputConnection) { |
|||
this.inputConnection = inputConnection; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onEmojiButtonToggle() { |
|||
if (getVisibility() == VISIBLE) setVisibility(GONE); |
|||
else setVisibility(VISIBLE); |
|||
return getVisibility() == VISIBLE; |
|||
} |
|||
|
|||
@Override |
|||
public void onEmojiInputFieldFocused(EmojiInputField emojiInputField) { |
|||
setInputConnection(emojiInputField.getInputConnection()); |
|||
} |
|||
|
|||
@Override |
|||
public void setVisibility(int visibility) { |
|||
//notify input fields
|
|||
for (EmojiInputField emojiInputField : emojiInputFields) |
|||
emojiInputField.onKeyboardVisibilityChange(visibility == VISIBLE); |
|||
super.setVisibility(visibility); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isVisible() { |
|||
return getVisibility() == VISIBLE; |
|||
} |
|||
|
|||
class Emoji { |
|||
final int src; |
|||
final String bbcode; |
|||
|
|||
public Emoji(int src, String bbcode) { |
|||
this.src = src; |
|||
this.bbcode = bbcode; |
|||
} |
|||
|
|||
public int getSrc() { |
|||
return src; |
|||
} |
|||
|
|||
public String getBbcode() { |
|||
return bbcode; |
|||
} |
|||
} |
|||
|
|||
class EmojiColumnSpanLookup extends GridLayoutManager.SpanSizeLookup { |
|||
|
|||
@Override |
|||
public int getSpanSize(int position) { |
|||
switch (emojis[position].getSrc()) { |
|||
case R.drawable.emoji_wav: |
|||
return 4; |
|||
case R.drawable.emoji_band: |
|||
return 3; |
|||
case R.drawable.emoji_pcsleep: |
|||
return 2; |
|||
default: |
|||
return 1; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
import android.graphics.drawable.AnimationDrawable; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v7.widget.AppCompatImageButton; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
|
|||
public class EmojiKeyboardAdapter extends RecyclerView.Adapter<EmojiKeyboardAdapter.EmojiViewHolder> { |
|||
private EmojiKeyboard.Emoji[] emojiIds; |
|||
private OnEmojiClickListener listener; |
|||
|
|||
public EmojiKeyboardAdapter(EmojiKeyboard.Emoji[] emojiIds) { |
|||
this.emojiIds = emojiIds; |
|||
} |
|||
|
|||
public void setOnEmojiClickListener(OnEmojiClickListener listener) { |
|||
this.listener = listener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
|||
AppCompatImageButton emojiButton = (AppCompatImageButton) LayoutInflater.from(parent.getContext()) |
|||
.inflate(R.layout.emoji_keyboard_grid_cell, parent, false); |
|||
return new EmojiViewHolder(emojiButton); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull EmojiViewHolder holder, int position) { |
|||
holder.emojiButton.setOnClickListener(view -> listener.onEmojiClick(view, position)); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewAttachedToWindow(@NonNull EmojiViewHolder holder) { |
|||
holder.emojiButton.setImageResource(emojiIds[holder.getAdapterPosition()].getSrc()); |
|||
if (holder.emojiButton.getDrawable() instanceof AnimationDrawable) { |
|||
AnimationDrawable emojiAnimation = (AnimationDrawable) holder.emojiButton.getDrawable(); |
|||
emojiAnimation.start(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return emojiIds.length; |
|||
} |
|||
|
|||
static class EmojiViewHolder extends RecyclerView.ViewHolder { |
|||
AppCompatImageButton emojiButton; |
|||
|
|||
EmojiViewHolder(AppCompatImageButton emojiButton) { |
|||
super(emojiButton); |
|||
this.emojiButton = emojiButton; |
|||
} |
|||
} |
|||
|
|||
interface OnEmojiClickListener { |
|||
void onEmojiClick(View view, int position); |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
import android.support.annotation.NonNull; |
|||
import android.support.v7.widget.AppCompatImageButton; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
|
|||
public class FormatButtonsAdapter extends RecyclerView.Adapter<FormatButtonsAdapter.FormatButtonViewHolder> { |
|||
private OnFormatButtonClickListener listener; |
|||
|
|||
public static final int[] FORMAT_BUTTON_IDS = {R.drawable.ic_format_bold, R.drawable.ic_format_italic, |
|||
R.drawable.ic_format_underlined, R.drawable.ic_strikethrough_s, R.drawable.ic_format_color_text, |
|||
R.drawable.ic_format_size, R.drawable.ic_text_format, R.drawable.ic_format_list_bulleted, |
|||
R.drawable.ic_format_align_left, R.drawable.ic_format_align_center, R.drawable.ic_format_align_right, |
|||
R.drawable.ic_insert_link, R.drawable.ic_format_quote, R.drawable.ic_code, R.drawable.ic_functions}; |
|||
|
|||
public FormatButtonsAdapter(OnFormatButtonClickListener listener) { |
|||
this.listener = listener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public FormatButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
|||
AppCompatImageButton formatButton = (AppCompatImageButton) LayoutInflater.from(parent.getContext()) |
|||
.inflate(R.layout.format_button_grid_cell, parent, false); |
|||
return new FormatButtonViewHolder(formatButton); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull FormatButtonViewHolder holder, int position) { |
|||
holder.formatButton.setImageResource(FORMAT_BUTTON_IDS[position]); |
|||
holder.formatButton.setOnClickListener(v -> |
|||
listener.onFormatButtonClick(v, FORMAT_BUTTON_IDS[holder.getAdapterPosition()])); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return FORMAT_BUTTON_IDS.length; |
|||
} |
|||
|
|||
static class FormatButtonViewHolder extends RecyclerView.ViewHolder { |
|||
AppCompatImageButton formatButton; |
|||
FormatButtonViewHolder(AppCompatImageButton formatButton) { |
|||
super(formatButton); |
|||
this.formatButton = formatButton; |
|||
} |
|||
} |
|||
|
|||
public interface OnFormatButtonClickListener { |
|||
void onFormatButtonClick(View view, int drawableId); |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
package gr.thmmy.mthmmy.editorview; |
|||
|
|||
public interface IEmojiKeyboard { |
|||
/** |
|||
* Hide keyboard |
|||
*/ |
|||
void hide(); |
|||
|
|||
/** |
|||
* Check if keyboard is visible |
|||
* @return true, if {@link EmojiKeyboard#getVisibility()} returns View.VISIBLE, otherwise false |
|||
*/ |
|||
boolean isVisible(); |
|||
|
|||
/** |
|||
* Callback to the keyboard when {@link EditorView#emojiButton} is clicked |
|||
* @return whether the keyboard became visible or not |
|||
*/ |
|||
boolean onEmojiButtonToggle(); |
|||
|
|||
/** |
|||
* Callback to create input connection with {@link EmojiInputField} |
|||
* @param emojiInputField the connected input field |
|||
*/ |
|||
void onEmojiInputFieldFocused(EmojiInputField emojiInputField); |
|||
|
|||
/** |
|||
* Persist a set of all input fields to update all of them when visibility changes |
|||
* @param emojiInputField the input field to be added |
|||
*/ |
|||
void registerEmojiInputField(EmojiInputField emojiInputField); |
|||
} |
@ -0,0 +1,108 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import java.text.DecimalFormat; |
|||
|
|||
public class Poll extends TopicItem { |
|||
public static final int TYPE_POLL = 3; |
|||
|
|||
private final String question; |
|||
private Entry[] entries; |
|||
private int availableVoteCount; |
|||
private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl; |
|||
|
|||
public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc, |
|||
String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl) { |
|||
this.question = question; |
|||
this.entries = entries; |
|||
this.availableVoteCount = availableVoteCount; |
|||
this.pollFormUrl = pollFormUrl; |
|||
this.sc = sc; |
|||
this.removeVoteUrl = removeVoteUrl; |
|||
this.showVoteResultsUrl = showVoteResultsUrl; |
|||
this.showOptionsUrl = showOptionsUrl; |
|||
} |
|||
|
|||
private int totalVotes() { |
|||
int sum = 0; |
|||
for (Entry entry : entries) { |
|||
sum += entry.votes; |
|||
} |
|||
return sum; |
|||
} |
|||
|
|||
public String getVotePercentage(int index) { |
|||
DecimalFormat format = new DecimalFormat(".#"); |
|||
double percentage = 100 * ((double) entries[index].votes / (double) totalVotes()); |
|||
return format.format(percentage); |
|||
} |
|||
|
|||
public String getQuestion() { |
|||
return question; |
|||
} |
|||
|
|||
public Entry[] getEntries() { |
|||
return entries; |
|||
} |
|||
|
|||
public int getAvailableVoteCount() { |
|||
return availableVoteCount; |
|||
} |
|||
|
|||
public String getPollFormUrl() { |
|||
return pollFormUrl; |
|||
} |
|||
|
|||
public String getSc() { |
|||
return sc; |
|||
} |
|||
|
|||
public String getRemoveVoteUrl() { |
|||
return removeVoteUrl; |
|||
} |
|||
|
|||
public String getShowVoteResultsUrl() { |
|||
return showVoteResultsUrl; |
|||
} |
|||
|
|||
public String getShowOptionsUrl() { |
|||
return showOptionsUrl; |
|||
} |
|||
|
|||
public static class Entry { |
|||
private final String entryName; |
|||
private int votes; |
|||
|
|||
public Entry(String entryName, int votes) { |
|||
this.entryName = entryName; |
|||
this.votes = votes; |
|||
} |
|||
|
|||
/** |
|||
* Constructor for entry with unknown number of votes |
|||
* |
|||
* @param entryName |
|||
* The name of the entry |
|||
*/ |
|||
public Entry(String entryName) { |
|||
this.entryName = entryName; |
|||
votes = -1; |
|||
} |
|||
|
|||
public String getEntryName() { |
|||
return entryName; |
|||
} |
|||
|
|||
public int getVotes() { |
|||
return votes; |
|||
} |
|||
|
|||
public void setVotes(int votes) { |
|||
this.votes = votes; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Vote label:" + entryName + ", num votes:" + votes; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
public abstract class TopicItem { |
|||
|
|||
} |
@ -0,0 +1,37 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
public class UploadCategory { |
|||
private String value, categoryTitle; |
|||
private ArrayList<UploadCategory> subCategories = new ArrayList<>(); |
|||
|
|||
private UploadCategory() { |
|||
//Disables default constructor
|
|||
} |
|||
|
|||
public UploadCategory(String value, String categoryTitle) { |
|||
this.value = value; |
|||
this.categoryTitle = categoryTitle; |
|||
} |
|||
|
|||
public String getValue() { |
|||
return value; |
|||
} |
|||
|
|||
public String getCategoryTitle() { |
|||
return categoryTitle; |
|||
} |
|||
|
|||
public void addSubCategory(String value, String categoryTitle) { |
|||
subCategories.add(new UploadCategory(value, categoryTitle)); |
|||
} |
|||
|
|||
public ArrayList<UploadCategory> getSubCategories() { |
|||
return subCategories; |
|||
} |
|||
|
|||
public boolean hasSubCategories() { |
|||
return !subCategories.isEmpty(); |
|||
} |
|||
} |
@ -0,0 +1,109 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.support.v7.widget.AppCompatSpinner; |
|||
import android.util.AttributeSet; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.AdapterView; |
|||
import android.widget.SpinnerAdapter; |
|||
import android.widget.TextView; |
|||
|
|||
import java.lang.reflect.InvocationHandler; |
|||
import java.lang.reflect.InvocationTargetException; |
|||
import java.lang.reflect.Method; |
|||
|
|||
public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner { |
|||
public AppCompatSpinnerWithoutDefault(Context context) { |
|||
super(context); |
|||
} |
|||
|
|||
public AppCompatSpinnerWithoutDefault(Context context, AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
public AppCompatSpinnerWithoutDefault(Context context, AttributeSet attrs, int defStyle) { |
|||
super(context, attrs, defStyle); |
|||
} |
|||
|
|||
@Override |
|||
@SuppressLint("PrivateApi") |
|||
public void setAdapter(SpinnerAdapter orig) { |
|||
final SpinnerAdapter adapter = newProxy(orig); |
|||
|
|||
super.setAdapter(adapter); |
|||
|
|||
try { |
|||
final Method m = AdapterView.class.getDeclaredMethod( |
|||
"setNextSelectedPositionInt", int.class); |
|||
m.setAccessible(true); |
|||
m.invoke(this, -1); |
|||
|
|||
final Method n = AdapterView.class.getDeclaredMethod( |
|||
"setSelectedPositionInt", int.class); |
|||
n.setAccessible(true); |
|||
n.invoke(this, -1); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
protected SpinnerAdapter newProxy(SpinnerAdapter obj) { |
|||
return (SpinnerAdapter) java.lang.reflect.Proxy.newProxyInstance( |
|||
obj.getClass().getClassLoader(), |
|||
new Class[]{SpinnerAdapter.class}, |
|||
new SpinnerAdapterProxy(obj)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Intercepts getView() to display the prompt if position < 0 |
|||
*/ |
|||
protected class SpinnerAdapterProxy implements InvocationHandler { |
|||
|
|||
SpinnerAdapter obj; |
|||
Method getView; |
|||
|
|||
|
|||
SpinnerAdapterProxy(SpinnerAdapter obj) { |
|||
this.obj = obj; |
|||
try { |
|||
this.getView = SpinnerAdapter.class.getMethod( |
|||
"getView", int.class, View.class, ViewGroup.class); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { |
|||
try { |
|||
return m.equals(getView) && |
|||
(Integer) (args[0]) < 0 ? |
|||
getView((Integer) args[0], (View) args[1], (ViewGroup) args[2]) : |
|||
m.invoke(obj, args); |
|||
} catch (InvocationTargetException e) { |
|||
throw e.getTargetException(); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
View getView(int position, View convertView, ViewGroup parent) { |
|||
if (position < 0) { |
|||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
|||
|
|||
if (inflater != null) { |
|||
final TextView v = |
|||
(TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false); |
|||
v.setText(getPrompt()); |
|||
return v; |
|||
} |
|||
return null; |
|||
} |
|||
return obj.getView(position, convertView, parent); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,42 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import com.crashlytics.android.Crashlytics; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; |
|||
|
|||
public class CrashReporter { |
|||
private static final int STRING_BATCH_LENGTH = 250; |
|||
|
|||
private CrashReporter() {} |
|||
|
|||
public static void reportDocument(Document document, String key) { |
|||
String documentString = document.toString(); |
|||
|
|||
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); |
|||
Elements postRows; |
|||
if (language.is(ParseHelpers.Language.GREEK)) |
|||
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)"); |
|||
else |
|||
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); |
|||
for (Element thisRow : postRows) { |
|||
String subject = thisRow.select("div[id^=subject_]").first().select("a").first().text(); |
|||
documentString = documentString.replace(subject, "subject"); |
|||
String post = thisRow.select("div").select(".post").first().text(); |
|||
documentString = documentString.replace(post, "post"); |
|||
} |
|||
|
|||
int batchCount = documentString.length() / STRING_BATCH_LENGTH; |
|||
for (int i = 0; i < batchCount; i++) { |
|||
String batch; |
|||
if (i != batchCount - 1) |
|||
batch = documentString.substring(i * STRING_BATCH_LENGTH, (i + 1) * STRING_BATCH_LENGTH); |
|||
else |
|||
batch = documentString.substring(i * STRING_BATCH_LENGTH); |
|||
Crashlytics.setString(key + "_" + i + 1, batch); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,80 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
public abstract class ExternalAsyncTask<U, V> extends AsyncTask<U, Void, V> { |
|||
|
|||
protected OnTaskStartedListener onTaskStartedListener; |
|||
protected OnTaskCancelledListener onTaskCancelledListener; |
|||
protected OnTaskFinishedListener<V> onTaskFinishedListener; |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (onTaskStartedListener != null) |
|||
onTaskStartedListener.onTaskStarted(); |
|||
else |
|||
super.onPreExecute(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onCancelled() { |
|||
if (onTaskCancelledListener != null) |
|||
onTaskCancelledListener.onTaskCanceled(); |
|||
else |
|||
super.onCancelled(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onCancelled(V v) { |
|||
if (onTaskCancelledListener != null) |
|||
onTaskCancelledListener.onTaskCanceled(); |
|||
else |
|||
super.onCancelled(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(V v) { |
|||
if (onTaskFinishedListener != null) |
|||
onTaskFinishedListener.onTaskFinished(v); |
|||
else |
|||
super.onPostExecute(v); |
|||
} |
|||
|
|||
public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener, |
|||
OnTaskFinishedListener<V> onTaskFinishedListener) { |
|||
this.onTaskStartedListener = onTaskStartedListener; |
|||
this.onTaskCancelledListener = onTaskCancelledListener; |
|||
this.onTaskFinishedListener = onTaskFinishedListener; |
|||
} |
|||
|
|||
public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskFinishedListener<V> onTaskFinishedListener) { |
|||
this.onTaskStartedListener = onTaskStartedListener; |
|||
this.onTaskFinishedListener = onTaskFinishedListener; |
|||
} |
|||
|
|||
public ExternalAsyncTask() { } |
|||
|
|||
public void setOnTaskStartedListener(OnTaskStartedListener onTaskStartedListener) { |
|||
this.onTaskStartedListener = onTaskStartedListener; |
|||
} |
|||
|
|||
public void setOnTaskCancelledListener(OnTaskCancelledListener onTaskCancelledListener) { |
|||
this.onTaskCancelledListener = onTaskCancelledListener; |
|||
} |
|||
|
|||
public void setOnTaskFinishedListener(OnTaskFinishedListener<V> onTaskFinishedListener) { |
|||
this.onTaskFinishedListener = onTaskFinishedListener; |
|||
} |
|||
|
|||
public interface OnTaskStartedListener { |
|||
void onTaskStarted(); |
|||
} |
|||
|
|||
public interface OnTaskCancelledListener { |
|||
void onTaskCanceled(); |
|||
} |
|||
|
|||
public interface OnTaskFinishedListener<V> { |
|||
void onTaskFinished(V result); |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Intent; |
|||
import android.net.Uri; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.text.Html; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.style.ClickableSpan; |
|||
import android.text.style.URLSpan; |
|||
import android.view.View; |
|||
|
|||
import gr.thmmy.mthmmy.activities.board.BoardActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.model.ThmmyPage; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; |
|||
|
|||
public class HTMLUtils { |
|||
private HTMLUtils() {} |
|||
|
|||
public static SpannableStringBuilder getSpannableFromHtml(Activity activity, String html) { |
|||
CharSequence sequence; |
|||
if (Build.VERSION.SDK_INT >= 24) { |
|||
sequence = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); |
|||
} else { |
|||
//noinspection deprecation
|
|||
sequence = Html.fromHtml(html); |
|||
} |
|||
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence); |
|||
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class); |
|||
for (URLSpan span : urls) { |
|||
makeLinkClickable(activity, strBuilder, span); |
|||
} |
|||
return strBuilder; |
|||
} |
|||
|
|||
private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) { |
|||
int start = strBuilder.getSpanStart(span); |
|||
int end = strBuilder.getSpanEnd(span); |
|||
int flags = strBuilder.getSpanFlags(span); |
|||
ClickableSpan clickable = new ClickableSpan() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL())); |
|||
if (target.is(ThmmyPage.PageCategory.BOARD)) { |
|||
Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_BOARD_URL, span.getURL()); |
|||
extras.putString(BUNDLE_BOARD_TITLE, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
activity.getApplicationContext().startActivity(intent); |
|||
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) { |
|||
Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, span.getURL()); |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); |
|||
extras.putString(BUNDLE_PROFILE_USERNAME, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
activity.getApplicationContext().startActivity(intent); |
|||
} else if (target.is(ThmmyPage.PageCategory.INDEX)) |
|||
activity.finish(); |
|||
} |
|||
}; |
|||
strBuilder.setSpan(clickable, start, end, flags); |
|||
strBuilder.removeSpan(span); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.content.SharedPreferences; |
|||
import android.preference.PreferenceManager; |
|||
|
|||
import gr.thmmy.mthmmy.BuildConfig; |
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
|
|||
public class LaunchType { |
|||
public enum LAUNCH_TYPE { |
|||
FIRST_LAUNCH_EVER, FIRST_LAUNCH_AFTER_UPDATE, NORMAL_LAUNCH, INDETERMINATE |
|||
} |
|||
|
|||
private static final String PREF_VERSION_CODE_KEY = "VERSION_CODE"; |
|||
|
|||
public static LAUNCH_TYPE getLaunchType() { |
|||
final int notThere = -1; |
|||
//Gets current version code
|
|||
int currentVersionCode = BuildConfig.VERSION_CODE; |
|||
//Gets saved version code
|
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(BaseApplication.getInstance().getContext()); |
|||
int savedVersionCode = prefs.getInt(PREF_VERSION_CODE_KEY, notThere); |
|||
//Checks for first run or upgrade
|
|||
if (currentVersionCode == savedVersionCode) { |
|||
//This is just a normal run
|
|||
return LAUNCH_TYPE.NORMAL_LAUNCH; |
|||
} else if (savedVersionCode == notThere) { |
|||
//Updates the shared preferences with the current version code
|
|||
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply(); |
|||
return LAUNCH_TYPE.FIRST_LAUNCH_EVER; |
|||
} else if (currentVersionCode > savedVersionCode) { |
|||
//Updates the shared preferences with the current version code
|
|||
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply(); |
|||
return LAUNCH_TYPE.FIRST_LAUNCH_AFTER_UPDATE; |
|||
} |
|||
//Probably shared preferences were manually changed by the user
|
|||
return LAUNCH_TYPE.INDETERMINATE; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,32 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
public class NetworkResultCodes { |
|||
/** |
|||
* The request was successful |
|||
*/ |
|||
public static final int SUCCESSFUL = 0; |
|||
/** |
|||
* Error 404, page was not found |
|||
*/ |
|||
public static final int NOT_FOUND = 1; |
|||
/** |
|||
* User session ended while posting the reply |
|||
*/ |
|||
public static final int SESSION_ENDED = 2; |
|||
/** |
|||
* Exception occured while parsing |
|||
*/ |
|||
public static final int PARSE_ERROR = 3; |
|||
/** |
|||
* Other undefined of unidentified error |
|||
*/ |
|||
public static final int OTHER_ERROR = 4; |
|||
/** |
|||
* Failed to connect to thmmy.gr |
|||
*/ |
|||
public static final int NETWORK_ERROR = 5; |
|||
/** |
|||
* Error while excecuting NetworkTask's performTask() |
|||
*/ |
|||
public static final int PERFORM_TASK_ERROR = 6; |
|||
} |
@ -0,0 +1,91 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseException; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import timber.log.Timber; |
|||
|
|||
public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>> { |
|||
|
|||
protected OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener; |
|||
|
|||
public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener, |
|||
OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) { |
|||
super(onTaskStartedListener, onTaskCancelledListener, null); |
|||
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener; |
|||
} |
|||
|
|||
public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) { |
|||
super(onTaskStartedListener, null); |
|||
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener; |
|||
} |
|||
|
|||
public NetworkTask() {} |
|||
|
|||
@Override |
|||
protected final Parcel<T> doInBackground(String... input) { |
|||
Response response; |
|||
try { |
|||
response = sendRequest(BaseApplication.getInstance().getClient(), input); |
|||
} catch (IOException e) { |
|||
Timber.e(e, "Error connecting to thmmy.gr"); |
|||
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); |
|||
} |
|||
String responseBodyString; |
|||
try { |
|||
responseBodyString = response.body().string(); |
|||
} catch (NullPointerException npe) { |
|||
Timber.wtf(npe, "Invalid response. Detatails: https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#body--"); |
|||
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); |
|||
} catch (IOException e) { |
|||
Timber.e(e, "Error getting response body string"); |
|||
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); |
|||
} |
|||
try { |
|||
T data = performTask(Jsoup.parse(responseBodyString), response); |
|||
int resultCode = getResultCode(response, data); |
|||
return new Parcel<>(resultCode, data); |
|||
} catch (ParseException pe) { |
|||
Timber.e(pe); |
|||
return new Parcel<>(NetworkResultCodes.PARSE_ERROR, null); |
|||
} catch (Exception e) { |
|||
Timber.e(e); |
|||
return new Parcel<>(NetworkResultCodes.PERFORM_TASK_ERROR, null); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Parcel<T> tParcel) { |
|||
if (onNetworkTaskFinishedListener != null) |
|||
onNetworkTaskFinishedListener.onNetworkTaskFinished(tParcel.getResultCode(), tParcel.getData()); |
|||
else |
|||
super.onPostExecute(tParcel); |
|||
} |
|||
|
|||
protected Response sendRequest(OkHttpClient client, String... input) throws IOException { |
|||
String url = input[0]; |
|||
Request request = new Request.Builder() |
|||
.url(url) |
|||
.build(); |
|||
return client.newCall(request).execute(); |
|||
} |
|||
|
|||
protected abstract T performTask(Document document, Response response); |
|||
|
|||
protected abstract int getResultCode(Response response, T data); |
|||
|
|||
public void setOnNetworkTaskFinishedListener(OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) { |
|||
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener; |
|||
} |
|||
|
|||
public interface OnNetworkTaskFinishedListener<T> { |
|||
void onNetworkTaskFinished(int resultCode, T data); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
public class Parcel<T> { |
|||
|
|||
private int resultCode; |
|||
private T data; |
|||
|
|||
public Parcel(int resultCode, T data) { |
|||
this.resultCode = resultCode; |
|||
this.data = data; |
|||
} |
|||
|
|||
public int getResultCode() { |
|||
return resultCode; |
|||
} |
|||
|
|||
public T getData() { |
|||
return data; |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
package gr.thmmy.mthmmy.utils.parsing; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import gr.thmmy.mthmmy.utils.NetworkTask; |
|||
import okhttp3.Response; |
|||
|
|||
public abstract class NewParseTask<T> extends NetworkTask<T> { |
|||
|
|||
public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener, |
|||
OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) { |
|||
super(onTaskStartedListener, onTaskCancelledListener, onParseTaskFinishedListener); |
|||
} |
|||
|
|||
public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) { |
|||
super(onTaskStartedListener, onParseTaskFinishedListener); |
|||
} |
|||
|
|||
public NewParseTask() {} |
|||
|
|||
@Override |
|||
protected final T performTask(Document document, Response response) { |
|||
try { |
|||
return parse(document, response); |
|||
} catch (Exception e) { |
|||
throw new ParseException("Parse failed.", e); |
|||
} |
|||
} |
|||
|
|||
protected abstract T parse (Document document, Response response) throws ParseException; |
|||
} |
@ -0,0 +1,18 @@ |
|||
package gr.thmmy.mthmmy.viewmodel; |
|||
|
|||
import android.arch.lifecycle.LiveData; |
|||
import android.arch.lifecycle.MutableLiveData; |
|||
import android.arch.lifecycle.ViewModel; |
|||
|
|||
import gr.thmmy.mthmmy.model.Bookmark; |
|||
|
|||
public class BaseViewModel extends ViewModel { |
|||
protected MutableLiveData<Bookmark> currentPageBookmark; |
|||
|
|||
public LiveData<Bookmark> getCurrentPageBookmark() { |
|||
if (currentPageBookmark == null) { |
|||
currentPageBookmark = new MutableLiveData<>(); |
|||
} |
|||
return currentPageBookmark; |
|||
} |
|||
} |
@ -0,0 +1,484 @@ |
|||
package gr.thmmy.mthmmy.viewmodel; |
|||
|
|||
import android.arch.lifecycle.MutableLiveData; |
|||
import android.content.Context; |
|||
import android.content.SharedPreferences; |
|||
import android.os.AsyncTask; |
|||
import android.preference.PreferenceManager; |
|||
import android.widget.CheckBox; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.RadioGroup; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
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.RemoveVoteTask; |
|||
import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask; |
|||
import gr.thmmy.mthmmy.activities.topic.tasks.SubmitVoteTask; |
|||
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask; |
|||
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTaskResult; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Poll; |
|||
import gr.thmmy.mthmmy.model.Post; |
|||
import gr.thmmy.mthmmy.model.TopicItem; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import gr.thmmy.mthmmy.utils.ExternalAsyncTask; |
|||
import gr.thmmy.mthmmy.utils.NetworkTask; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; |
|||
import timber.log.Timber; |
|||
|
|||
public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted, |
|||
PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished { |
|||
/** |
|||
* topic state |
|||
*/ |
|||
private boolean editingPost = false; |
|||
private boolean writingReply = false; |
|||
/** |
|||
* A list of {@link Post#getPostIndex()} for building quotes for replying |
|||
*/ |
|||
private ArrayList<Integer> toQuoteList = new ArrayList<>(); |
|||
/** |
|||
* caches the expand/collapse state of the user extra info in the current page for the recyclerview |
|||
*/ |
|||
private ArrayList<Boolean> isUserExtraInfoVisibile = new ArrayList<>(); |
|||
/** |
|||
* holds the adapter position of the post being edited |
|||
*/ |
|||
private int postBeingEditedPosition; |
|||
|
|||
private TopicTask currentTopicTask; |
|||
private PrepareForEditTask currentPrepareForEditTask; |
|||
private PrepareForReply currentPrepareForReplyTask; |
|||
|
|||
//callbacks for topic activity
|
|||
private TopicTask.TopicTaskObserver topicTaskObserver; |
|||
private ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener; |
|||
private NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener; |
|||
private ReplyTask.ReplyTaskCallbacks replyFinishListener; |
|||
private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks; |
|||
private EditTask.EditTaskCallbacks editTaskCallbacks; |
|||
private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks; |
|||
private ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener; |
|||
private NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener; |
|||
private ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener; |
|||
private NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener; |
|||
|
|||
/** |
|||
* Holds the value (index) of the page to be requested when a user interaction with bottom |
|||
* navigation bar occurs, aka the value that the page indicator shows |
|||
*/ |
|||
private MutableLiveData<Integer> pageIndicatorIndex = new MutableLiveData<>(); |
|||
|
|||
private MutableLiveData<String> replyPageUrl = new MutableLiveData<>(); |
|||
private MutableLiveData<Integer> pageTopicId = new MutableLiveData<>(); |
|||
private MutableLiveData<String> topicTitle = new MutableLiveData<>(); |
|||
private MutableLiveData<ArrayList<TopicItem>> topicItems = new MutableLiveData<>(); |
|||
private MutableLiveData<Integer> focusedPostIndex = new MutableLiveData<>(); |
|||
private MutableLiveData<TopicTask.ResultCode> topicTaskResultCode = new MutableLiveData<>(); |
|||
private MutableLiveData<String> topicTreeAndMods = new MutableLiveData<>(); |
|||
private MutableLiveData<String> topicViewers = new MutableLiveData<>(); |
|||
private String topicUrl; |
|||
private int currentPageIndex; |
|||
private int pageCount; |
|||
|
|||
private MutableLiveData<PrepareForReplyResult> prepareForReplyResult = new MutableLiveData<>(); |
|||
private MutableLiveData<PrepareForEditResult> prepareForEditResult = new MutableLiveData<>(); |
|||
|
|||
public void loadUrl(String pageUrl) { |
|||
stopLoading(); |
|||
topicUrl = pageUrl; |
|||
currentTopicTask = new TopicTask(topicTaskObserver, this); |
|||
currentTopicTask.execute(pageUrl); |
|||
} |
|||
|
|||
public void reloadPage() { |
|||
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); |
|||
Timber.i("Reloading page"); |
|||
loadUrl(topicUrl); |
|||
} |
|||
|
|||
public void reloadPageThen(Runnable runnable) { |
|||
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); |
|||
Timber.i("Reloading page"); |
|||
stopLoading(); |
|||
currentTopicTask = new TopicTask(topicTaskObserver, result -> { |
|||
TopicViewModel.this.onTopicTaskCompleted(result); |
|||
runnable.run(); |
|||
}); |
|||
currentTopicTask.execute(topicUrl); |
|||
} |
|||
|
|||
/** |
|||
* In contrasto to {@link TopicViewModel#reloadPage()} this method gets rid of any arguements |
|||
* in the url before refreshing |
|||
*/ |
|||
public void resetPage() { |
|||
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); |
|||
Timber.i("Reseting page"); |
|||
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15)); |
|||
} |
|||
|
|||
public void loadPageIndicated() { |
|||
if (pageIndicatorIndex.getValue() == null) |
|||
throw new NullPointerException("No page has been loaded yet!"); |
|||
int pageRequested = pageIndicatorIndex.getValue() - 1; |
|||
if (pageRequested != currentPageIndex - 1) { |
|||
Timber.i("Changing to page " + pageRequested + 1); |
|||
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(pageRequested * 15)); |
|||
pageIndicatorIndex.setValue(pageRequested + 1); |
|||
} else { |
|||
stopLoading(); |
|||
} |
|||
} |
|||
|
|||
public boolean submitVote(LinearLayout optionsLayout) { |
|||
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!"); |
|||
ArrayList<Integer> votes = new ArrayList<>(); |
|||
if (optionsLayout.getChildAt(0) instanceof RadioGroup) { |
|||
RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0); |
|||
votes.add(optionsRadioGroup.getCheckedRadioButtonId()); |
|||
} else if (optionsLayout.getChildAt(0) instanceof LinearLayout) { |
|||
for (int i = 0; i < optionsLayout.getChildCount(); i++) { |
|||
LinearLayout container = (LinearLayout) optionsLayout.getChildAt(i); |
|||
if (((CheckBox) container.getChildAt(0)).isChecked()) |
|||
votes.add(i); |
|||
} |
|||
} |
|||
int[] votesArray = new int[votes.size()]; |
|||
for (int i = 0; i < votes.size(); i++) votesArray[i] = votes.get(i); |
|||
Poll poll = (Poll) topicItems.getValue().get(0); |
|||
if (poll.getAvailableVoteCount() < votesArray.length) return false; |
|||
SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray); |
|||
submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener); |
|||
submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener); |
|||
submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc()); |
|||
return true; |
|||
} |
|||
|
|||
public void removeVote() { |
|||
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!"); |
|||
RemoveVoteTask removeVoteTask = new RemoveVoteTask(); |
|||
removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener); |
|||
removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener); |
|||
removeVoteTask.execute(((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl()); |
|||
} |
|||
|
|||
public void prepareForReply() { |
|||
if (replyPageUrl.getValue() == null) |
|||
throw new NullPointerException("Topic task has not finished yet!"); |
|||
stopLoading(); |
|||
setPageIndicatorIndex(pageCount, true); |
|||
Timber.i("Preparing for reply"); |
|||
currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this, |
|||
replyPageUrl.getValue()); |
|||
currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0])); |
|||
} |
|||
|
|||
public void postReply(Context context, String subject, String reply) { |
|||
if (prepareForReplyResult.getValue() == null) { |
|||
throw new NullPointerException("Reply preparation was not found!"); |
|||
} |
|||
PrepareForReplyResult replyForm = prepareForReplyResult.getValue(); |
|||
boolean includeAppSignature = true; |
|||
SessionManager sessionManager = BaseActivity.getSessionManager(); |
|||
if (sessionManager.isLoggedIn()) { |
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
|||
includeAppSignature = prefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true); |
|||
} |
|||
toQuoteList.clear(); |
|||
Timber.i("Posting reply"); |
|||
new ReplyTask(replyFinishListener, includeAppSignature).execute(subject, reply, |
|||
replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic()); |
|||
} |
|||
|
|||
public void deletePost(String postDeleteUrl) { |
|||
Timber.i("Deleting post"); |
|||
new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).execute(postDeleteUrl); |
|||
} |
|||
|
|||
public void prepareForEdit(int position, String postEditURL) { |
|||
if (replyPageUrl.getValue() == null) |
|||
throw new NullPointerException("Topic task has not finished yet!"); |
|||
stopLoading(); |
|||
Timber.i("Preparing for edit"); |
|||
currentPrepareForEditTask = new PrepareForEditTask(prepareForEditCallbacks, this, position, |
|||
replyPageUrl.getValue()); |
|||
currentPrepareForEditTask.execute(postEditURL); |
|||
} |
|||
|
|||
public void editPost(int position, String subject, String message) { |
|||
if (prepareForEditResult.getValue() == null) |
|||
throw new NullPointerException("Edit preparation was not found!"); |
|||
PrepareForEditResult editResult = prepareForEditResult.getValue(); |
|||
Timber.i("Editing post"); |
|||
new EditTask(editTaskCallbacks, position).execute(editResult.getCommitEditUrl(), message, |
|||
editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic()); |
|||
} |
|||
|
|||
/** |
|||
* cancel tasks that change the ui |
|||
* topic, prepare for edit, prepare for reply tasks need to cancel all other ui changing tasks |
|||
* before starting |
|||
*/ |
|||
public void stopLoading() { |
|||
if (currentTopicTask != null && currentTopicTask.getStatus() == AsyncTask.Status.RUNNING) { |
|||
Timber.i("Canceling topic task"); |
|||
currentTopicTask.cancel(true); |
|||
pageIndicatorIndex.setValue(currentPageIndex); |
|||
topicTaskObserver.onTopicTaskCancelled(); |
|||
} |
|||
if (currentPrepareForEditTask != null && currentPrepareForEditTask.getStatus() == AsyncTask.Status.RUNNING) { |
|||
Timber.i("Canceling prepare for edit task"); |
|||
currentPrepareForEditTask.cancel(true); |
|||
prepareForEditCallbacks.onPrepareEditCancelled(); |
|||
} |
|||
if (currentPrepareForReplyTask != null && currentPrepareForReplyTask.getStatus() == AsyncTask.Status.RUNNING) { |
|||
Timber.i("Canceling prepare for reply task"); |
|||
currentPrepareForReplyTask.cancel(true); |
|||
prepareForReplyCallbacks.onPrepareForReplyCancelled(); |
|||
} |
|||
// no need to cancel reply, edit and delete task, user should not have to wait for the ui
|
|||
// after he is done posting, editing or deleting
|
|||
} |
|||
|
|||
// callbacks for viewmodel
|
|||
@Override |
|||
public void onTopicTaskCompleted(TopicTaskResult result) { |
|||
if (result.getResultCode() == TopicTask.ResultCode.SUCCESS) { |
|||
currentPageIndex = result.getCurrentPageIndex(); |
|||
pageCount = result.getPageCount(); |
|||
topicTreeAndMods.setValue(result.getTopicTreeAndMods()); |
|||
topicViewers.setValue(result.getTopicViewers()); |
|||
pageTopicId.setValue(result.getLoadedPageTopicId()); |
|||
replyPageUrl.setValue(result.getReplyPageUrl()); |
|||
topicTitle.setValue(result.getTopicTitle()); |
|||
pageIndicatorIndex.setValue(result.getCurrentPageIndex()); |
|||
topicItems.setValue(result.getNewPostsList()); |
|||
focusedPostIndex.setValue(result.getFocusedPostIndex()); |
|||
isUserExtraInfoVisibile.clear(); |
|||
for (int i = 0; i < result.getNewPostsList().size(); i++) { |
|||
isUserExtraInfoVisibile.add(false); |
|||
} |
|||
} |
|||
topicTaskResultCode.setValue(result.getResultCode()); |
|||
} |
|||
|
|||
@Override |
|||
public void onPrepareForReplyFinished(PrepareForReplyResult result) { |
|||
prepareForReplyResult.setValue(result); |
|||
} |
|||
|
|||
@Override |
|||
public void onPrepareEditFinished(PrepareForEditResult result, int position) { |
|||
postBeingEditedPosition = position; |
|||
prepareForEditResult.setValue(result); |
|||
} |
|||
|
|||
public void incrementPageRequestValue(int step, boolean changePage) { |
|||
if (pageIndicatorIndex.getValue() == null) |
|||
throw new NullPointerException("No page has been loaded yet!"); |
|||
int oldIndicatorIndex = pageIndicatorIndex.getValue(); |
|||
if (oldIndicatorIndex <= pageCount - step) { |
|||
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step); |
|||
} else |
|||
pageIndicatorIndex.setValue(pageCount); |
|||
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); |
|||
} |
|||
|
|||
public void decrementPageRequestValue(int step, boolean changePage) { |
|||
if (pageIndicatorIndex.getValue() == null) |
|||
throw new NullPointerException("No page has been loaded yet!"); |
|||
int oldIndicatorIndex = pageIndicatorIndex.getValue(); |
|||
if (oldIndicatorIndex > step) { |
|||
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step); |
|||
} else |
|||
pageIndicatorIndex.setValue(1); |
|||
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); |
|||
} |
|||
|
|||
public void setPageIndicatorIndex(int pageIndicatorIndex, boolean changePage) { |
|||
if (this.pageIndicatorIndex.getValue() == null) |
|||
throw new NullPointerException("No page has been loaded yet!"); |
|||
int oldIndicatorIndex = this.pageIndicatorIndex.getValue(); |
|||
this.pageIndicatorIndex.setValue(pageIndicatorIndex); |
|||
if (changePage && oldIndicatorIndex != this.pageIndicatorIndex.getValue()) loadPageIndicated(); |
|||
} |
|||
|
|||
// <-------------Just getters, setters and helper methods below here---------------->
|
|||
|
|||
|
|||
public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) { |
|||
this.removeVoteTaskStartedListener = removeVoteTaskStartedListener; |
|||
} |
|||
|
|||
public void setRemoveVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener) { |
|||
this.removeVoteTaskFinishedListener = removeVoteTaskFinishedListener; |
|||
} |
|||
|
|||
public void setVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener) { |
|||
this.voteTaskStartedListener = voteTaskStartedListener; |
|||
} |
|||
|
|||
public void setVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener) { |
|||
this.voteTaskFinishedListener = voteTaskFinishedListener; |
|||
} |
|||
|
|||
public MutableLiveData<String> getTopicViewers() { |
|||
return topicViewers; |
|||
} |
|||
|
|||
public MutableLiveData<String> getTopicTreeAndMods() { |
|||
return topicTreeAndMods; |
|||
} |
|||
|
|||
public MutableLiveData<TopicTask.ResultCode> getTopicTaskResultCode() { |
|||
return topicTaskResultCode; |
|||
} |
|||
|
|||
public MutableLiveData<Integer> getFocusedPostIndex() { |
|||
return focusedPostIndex; |
|||
} |
|||
|
|||
public MutableLiveData<ArrayList<TopicItem>> getTopicItems() { |
|||
return topicItems; |
|||
} |
|||
|
|||
public MutableLiveData<String> getReplyPageUrl() { |
|||
return replyPageUrl; |
|||
} |
|||
|
|||
public MutableLiveData<Integer> getPageTopicId() { |
|||
return pageTopicId; |
|||
} |
|||
|
|||
public MutableLiveData<String> getTopicTitle() { |
|||
return topicTitle; |
|||
} |
|||
|
|||
public String getTopicUrl() { |
|||
return topicUrl; |
|||
} |
|||
|
|||
public MutableLiveData<Integer> getPageIndicatorIndex() { |
|||
return pageIndicatorIndex; |
|||
} |
|||
|
|||
public boolean isUserExtraInfoVisible(int position) { |
|||
return isUserExtraInfoVisibile.get(position); |
|||
} |
|||
|
|||
public void hideUserInfo(int position) { |
|||
isUserExtraInfoVisibile.set(position, false); |
|||
} |
|||
|
|||
public void toggleUserInfo(int position) { |
|||
isUserExtraInfoVisibile.set(position, !isUserExtraInfoVisibile.get(position)); |
|||
} |
|||
|
|||
public ArrayList<Integer> getToQuoteList() { |
|||
return toQuoteList; |
|||
} |
|||
|
|||
public void postIndexToggle(Integer postIndex) { |
|||
if (toQuoteList.contains(postIndex)) |
|||
toQuoteList.remove(postIndex); |
|||
else |
|||
toQuoteList.add(postIndex); |
|||
} |
|||
|
|||
public void setTopicTaskObserver(TopicTask.TopicTaskObserver topicTaskObserver) { |
|||
this.topicTaskObserver = topicTaskObserver; |
|||
} |
|||
|
|||
|
|||
public void setDeleteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener) { |
|||
this.deleteTaskStartedListener = deleteTaskStartedListener; |
|||
} |
|||
|
|||
public void setDeleteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener) { |
|||
this.deleteTaskFinishedListener = deleteTaskFinishedListener; |
|||
} |
|||
|
|||
public void setReplyFinishListener(ReplyTask.ReplyTaskCallbacks replyFinishListener) { |
|||
this.replyFinishListener = replyFinishListener; |
|||
} |
|||
|
|||
public void setPrepareForEditCallbacks(PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks) { |
|||
this.prepareForEditCallbacks = prepareForEditCallbacks; |
|||
} |
|||
|
|||
public void setEditTaskCallbacks(EditTask.EditTaskCallbacks editTaskCallbacks) { |
|||
this.editTaskCallbacks = editTaskCallbacks; |
|||
} |
|||
|
|||
public void setPrepareForReplyCallbacks(PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks) { |
|||
this.prepareForReplyCallbacks = prepareForReplyCallbacks; |
|||
} |
|||
|
|||
public MutableLiveData<PrepareForReplyResult> getPrepareForReplyResult() { |
|||
return prepareForReplyResult; |
|||
} |
|||
|
|||
public MutableLiveData<PrepareForEditResult> getPrepareForEditResult() { |
|||
return prepareForEditResult; |
|||
} |
|||
|
|||
public void setEditingPost(boolean editingPost) { |
|||
this.editingPost = editingPost; |
|||
} |
|||
|
|||
public boolean isEditingPost() { |
|||
return editingPost; |
|||
} |
|||
|
|||
public int getPostBeingEditedPosition() { |
|||
return postBeingEditedPosition; |
|||
} |
|||
|
|||
public boolean canReply() { |
|||
return replyPageUrl.getValue() != null; |
|||
} |
|||
|
|||
public boolean isWritingReply() { |
|||
return writingReply; |
|||
} |
|||
|
|||
public void setWritingReply(boolean writingReply) { |
|||
this.writingReply = writingReply; |
|||
} |
|||
|
|||
public int getCurrentPageIndex() { |
|||
if (currentPageIndex == 0) throw new NullPointerException("No page has been loaded yet!"); |
|||
return currentPageIndex; |
|||
} |
|||
|
|||
public int getPageCount() { |
|||
if (pageCount == 0) throw new NullPointerException("No page has been loaded yet!"); |
|||
return pageCount; |
|||
} |
|||
|
|||
public String getPostBeingEditedText() { |
|||
if (prepareForEditResult.getValue() == null) |
|||
throw new NullPointerException("Edit preparation was not found!"); |
|||
return prepareForEditResult.getValue().getPostText(); |
|||
} |
|||
|
|||
public String getBuildedQuotes() { |
|||
if (prepareForReplyResult.getValue() == null) |
|||
throw new NullPointerException("Reply preparation was not found"); |
|||
return prepareForReplyResult.getValue().getBuildedQuotes(); |
|||
} |
|||
|
|||
public int postCount() { |
|||
if (topicItems.getValue() == null) |
|||
throw new NullPointerException("No page has been loaded yet!"); |
|||
return topicItems.getValue().size(); |
|||
} |
|||
} |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 186 B |
Before Width: | Height: | Size: 293 B |
Before Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 258 B |
Before Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 595 B |
Before Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 541 B |
Before Width: | Height: | Size: 170 B |
Before Width: | Height: | Size: 148 B |
Before Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 161 B |
Before Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 376 B |
Before Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 365 B |
Before Width: | Height: | Size: 248 B |