mirror of https://github.com/ThmmyNoLife/mTHMMY
committed by
GitHub
37 changed files with 2324 additions and 518 deletions
@ -0,0 +1,126 @@ |
|||
package gr.thmmy.mthmmy.activities.create_pm; |
|||
|
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.os.Bundle; |
|||
import android.preference.PreferenceManager; |
|||
import android.text.InputType; |
|||
import android.text.TextUtils; |
|||
import android.view.View; |
|||
import android.view.inputmethod.EditorInfo; |
|||
import android.widget.Toast; |
|||
|
|||
import com.google.android.material.textfield.TextInputLayout; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
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 gr.thmmy.mthmmy.utils.ExternalAsyncTask; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import timber.log.Timber; |
|||
|
|||
public class CreatePMActivity extends BaseActivity implements ExternalAsyncTask.OnTaskStartedListener, ExternalAsyncTask.OnTaskFinishedListener<Boolean> { |
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private EditorView contentEditor; |
|||
private TextInputLayout subjectInput; |
|||
private EmojiKeyboard emojiKeyboard; |
|||
private String username, sendPmUrl; |
|||
/** |
|||
* Used for example in quotes to pre-populate the EditorView with quoted text |
|||
*/ |
|||
private String defaultContent; |
|||
|
|||
public static final String BUNDLE_SEND_PM_URL = "send-pm-url"; |
|||
public static final String BUNDLE_PM_CONTENT = "pm-content"; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_create_pm); |
|||
|
|||
Intent callingIntent = getIntent(); |
|||
username = callingIntent.getStringExtra(ProfileActivity.BUNDLE_PROFILE_USERNAME); |
|||
sendPmUrl = callingIntent.getStringExtra(BUNDLE_SEND_PM_URL); |
|||
defaultContent = callingIntent.getStringExtra(BUNDLE_PM_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); |
|||
|
|||
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 (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); |
|||
} |
|||
|
|||
SendPMTask sendPMTask = new SendPMTask(includeAppSignature); |
|||
sendPMTask.setOnTaskStartedListener(this); |
|||
sendPMTask.setOnTaskFinishedListener(this); |
|||
sendPMTask.execute(sendPmUrl, subjectInput.getEditText().getText().toString(), |
|||
contentEditor.getText().toString()); |
|||
}); |
|||
if (defaultContent != null) |
|||
contentEditor.setText(defaultContent); |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if (emojiKeyboard.getVisibility() == View.VISIBLE) { |
|||
emojiKeyboard.setVisibility(View.GONE); |
|||
} else { |
|||
super.onBackPressed(); |
|||
} |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void onTaskStarted() { |
|||
Timber.i("New pm started being sent"); |
|||
progressBar.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onTaskFinished(Boolean success) { |
|||
progressBar.setVisibility(View.INVISIBLE); |
|||
if (success) { |
|||
Timber.i("New pm sent successfully"); |
|||
Toast.makeText(this, "Personal message sent successfully", Toast.LENGTH_SHORT).show(); |
|||
finish(); |
|||
} else { |
|||
Timber.w("Failed to send pm"); |
|||
Toast.makeText(getBaseContext(), "Failed to send PM. Check your connection", Toast.LENGTH_LONG).show(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
package gr.thmmy.mthmmy.activities.create_pm; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import gr.thmmy.mthmmy.utils.ExternalAsyncTask; |
|||
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 SendPMTask extends ExternalAsyncTask<String, Boolean> { |
|||
|
|||
private boolean includeAppSignature; |
|||
|
|||
public SendPMTask(boolean includeAppSignature) { |
|||
this.includeAppSignature = includeAppSignature; |
|||
} |
|||
|
|||
@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, outbox, createTopicUrl, replied_to, folder, u; |
|||
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"); |
|||
outbox = document.select("input[name=outbox]").first().attr("value"); |
|||
replied_to = document.select("input[name=replied_to]").first().attr("value"); |
|||
folder = document.select("input[name=folder]").first().attr("value"); |
|||
u = document.select("input[name=u]").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("u", u) // recipient id
|
|||
.addFormDataPart("subject", strings[1]) |
|||
.addFormDataPart("outbox", outbox) |
|||
.addFormDataPart("replied_to", replied_to) |
|||
.addFormDataPart("folder", folder) |
|||
.build(); |
|||
|
|||
Request pmRequest = 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(pmRequest).execute(); |
|||
Response response2 = client.newCall(pmRequest).execute(); |
|||
switch (replyStatus(response2)) { |
|||
case SUCCESSFUL: |
|||
BaseApplication.getInstance().logFirebaseAnalyticsEvent("new_pm_sent", null); |
|||
return true; |
|||
default: |
|||
Timber.e("Malformed pmRequest. Request string: %s", pmRequest.toString()); |
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
package gr.thmmy.mthmmy.activities.create_content; |
|||
package gr.thmmy.mthmmy.activities.create_topic; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
@ -0,0 +1,94 @@ |
|||
package gr.thmmy.mthmmy.activities.inbox; |
|||
|
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.widget.ImageButton; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.lifecycle.ViewModel; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
import androidx.lifecycle.ViewModelProviders; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.pagination.BottomPaginationView; |
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import gr.thmmy.mthmmy.viewmodel.InboxViewModel; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import timber.log.Timber; |
|||
|
|||
public class InboxActivity extends BaseActivity { |
|||
|
|||
private InboxViewModel inboxViewModel; |
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private RecyclerView pmRecyclerview; |
|||
private InboxAdapter inboxAdapter; |
|||
private BottomPaginationView bottomPagination; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_inbox); |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = findViewById(R.id.toolbar); |
|||
toolbar.setTitle("Inbox"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(INBOX_ID); |
|||
|
|||
progressBar = findViewById(R.id.progress_bar); |
|||
pmRecyclerview = findViewById(R.id.inbox_recyclerview); |
|||
LinearLayoutManager layoutManager = new LinearLayoutManager(this); |
|||
pmRecyclerview.setLayoutManager(layoutManager); |
|||
inboxAdapter = new InboxAdapter(this); |
|||
pmRecyclerview.setAdapter(inboxAdapter); |
|||
bottomPagination = findViewById(R.id.bottom_pagination); |
|||
|
|||
inboxViewModel = new ViewModelProvider(this).get(InboxViewModel.class); |
|||
bottomPagination.setOnPageRequestedListener(inboxViewModel); |
|||
subscribeUI(); |
|||
|
|||
inboxViewModel.loadInbox(); |
|||
} |
|||
|
|||
private void subscribeUI() { |
|||
inboxViewModel.setOnInboxTaskStartedListener(() -> { |
|||
progressBar.setVisibility(View.VISIBLE); |
|||
Timber.d("inbox task started"); |
|||
}); |
|||
inboxViewModel.setOnInboxTaskFinishedListener((resultCode, inbox) -> { |
|||
progressBar.setVisibility(View.INVISIBLE); |
|||
if (resultCode == NetworkResultCodes.SUCCESSFUL) { |
|||
Timber.i("Successfully loaded inbox"); |
|||
inboxAdapter.notifyDataSetChanged(); |
|||
} else { |
|||
Timber.w("Failed to load inbox"); |
|||
Toast.makeText(this, "Failed to load inbox", Toast.LENGTH_SHORT).show(); |
|||
finish(); |
|||
} |
|||
}); |
|||
inboxViewModel.setOnInboxTaskCancelledListener(() -> { |
|||
progressBar.setVisibility(ProgressBar.GONE); |
|||
Timber.d("inbox task cancelled"); |
|||
}); |
|||
inboxViewModel.getPageIndicatorIndex().observe(this, pageIndicatorIndex -> { |
|||
if (pageIndicatorIndex == null) return; |
|||
bottomPagination.setIndicatedPageIndex(pageIndicatorIndex); |
|||
}); |
|||
inboxViewModel.getPageCount().observe(this, pageCount -> { |
|||
if (pageCount == null) return; |
|||
bottomPagination.setTotalPageCount(pageCount); |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,374 @@ |
|||
package gr.thmmy.mthmmy.activities.inbox; |
|||
|
|||
import android.annotation.TargetApi; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.graphics.Color; |
|||
import android.graphics.Typeface; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.text.TextUtils; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.webkit.WebResourceRequest; |
|||
import android.webkit.WebView; |
|||
import android.webkit.WebViewClient; |
|||
import android.widget.ImageButton; |
|||
import android.widget.ImageView; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.PopupWindow; |
|||
import android.widget.RelativeLayout; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.appcompat.content.res.AppCompatResources; |
|||
import androidx.core.content.res.ResourcesCompat; |
|||
import androidx.lifecycle.ViewModelProviders; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.board.BoardActivity; |
|||
import gr.thmmy.mthmmy.activities.create_pm.CreatePMActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.activities.topic.TopicActivity; |
|||
import gr.thmmy.mthmmy.model.PM; |
|||
import gr.thmmy.mthmmy.model.ThmmyPage; |
|||
import gr.thmmy.mthmmy.utils.CircleTransform; |
|||
import gr.thmmy.mthmmy.utils.MessageAnimations; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; |
|||
import gr.thmmy.mthmmy.viewmodel.InboxViewModel; |
|||
|
|||
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; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; |
|||
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.USER_COLOR_WHITE; |
|||
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.USER_COLOR_YELLOW; |
|||
|
|||
public class InboxAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
|||
|
|||
private Context context; |
|||
private InboxViewModel inboxViewModel; |
|||
|
|||
public InboxAdapter(InboxActivity context) { |
|||
this.context = context; |
|||
inboxViewModel = ViewModelProviders.of(context).get(InboxViewModel.class); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
|||
View itemView = LayoutInflater.from(parent.getContext()) |
|||
.inflate(R.layout.activity_inbox_pm_row, parent, false); |
|||
return new InboxAdapter.PMViewHolder(itemView); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { |
|||
InboxAdapter.PMViewHolder holder = (PMViewHolder) viewHolder; |
|||
PM currentPM = pms().get(position); |
|||
|
|||
//Post's WebView parameters
|
|||
holder.pm.setClickable(true); |
|||
holder.pm.setWebViewClient(new LinkLauncher()); |
|||
|
|||
Picasso.with(context) |
|||
.load(currentPM.getThumbnailUrl()) |
|||
.fit() |
|||
.centerCrop() |
|||
.error(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources() |
|||
, R.drawable.ic_default_user_avatar_darker, null))) |
|||
.placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources() |
|||
, R.drawable.ic_default_user_avatar_darker, null))) |
|||
.transform(new CircleTransform()) |
|||
.into(holder.thumbnail); |
|||
|
|||
//Sets username,submit date, index number, subject, post's and attached files texts
|
|||
holder.username.setText(currentPM.getAuthor()); |
|||
holder.pmDate.setText(currentPM.getPmDate()); |
|||
holder.subject.setText(currentPM.getSubject()); |
|||
holder.pm.loadDataWithBaseURL("file:///android_asset/", currentPM.getContent(), |
|||
"text/html", "UTF-8", null); |
|||
|
|||
// author info
|
|||
if (currentPM.getAuthorSpecialRank() != null && !currentPM.getAuthorSpecialRank().equals("")) { |
|||
holder.specialRank.setText(currentPM.getAuthorSpecialRank()); |
|||
holder.specialRank.setVisibility(View.VISIBLE); |
|||
} else holder.specialRank.setVisibility(View.GONE); |
|||
if (currentPM.getAuthorRank() != null && !currentPM.getAuthorRank().equals("")) { |
|||
holder.rank.setText(currentPM.getAuthorRank()); |
|||
holder.rank.setVisibility(View.VISIBLE); |
|||
} else holder.rank.setVisibility(View.GONE); |
|||
if (currentPM.getAuthorGender() != null && !currentPM.getAuthorGender().equals("")) { |
|||
holder.gender.setText(currentPM.getAuthorGender()); |
|||
holder.gender.setVisibility(View.VISIBLE); |
|||
} else holder.gender.setVisibility(View.GONE); |
|||
if (currentPM.getAuthorNumberOfPosts() != null && !currentPM.getAuthorNumberOfPosts().equals("")) { |
|||
holder.numberOfPosts.setText(currentPM.getAuthorNumberOfPosts()); |
|||
holder.numberOfPosts.setVisibility(View.VISIBLE); |
|||
} else holder.numberOfPosts.setVisibility(View.GONE); |
|||
if (currentPM.getAuthorPersonalText() != null && !currentPM.getAuthorPersonalText().equals("")) { |
|||
holder.personalText.setText(currentPM.getAuthorPersonalText()); |
|||
holder.personalText.setVisibility(View.VISIBLE); |
|||
} else holder.personalText.setVisibility(View.GONE); |
|||
if (currentPM.getAuthorColor() != USER_COLOR_YELLOW) |
|||
holder.username.setTextColor(currentPM.getAuthorColor()); |
|||
else holder.username.setTextColor(USER_COLOR_WHITE); |
|||
|
|||
if (currentPM.getAuthorNumberOfStars() > 0) { |
|||
holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() |
|||
, "fonts/fontawesome-webfont.ttf")); |
|||
|
|||
String aStar = context.getResources().getString(R.string.fa_icon_star); |
|||
StringBuilder usersStars = new StringBuilder(); |
|||
for (int i = 0; i < currentPM.getAuthorNumberOfStars(); ++i) { |
|||
usersStars.append(aStar); |
|||
} |
|||
holder.stars.setText(usersStars.toString()); |
|||
holder.stars.setTextColor(currentPM.getAuthorColor()); |
|||
holder.stars.setVisibility(View.VISIBLE); |
|||
} else holder.stars.setVisibility(View.GONE); |
|||
|
|||
if (currentPM.isUserMentioned()) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.mention_card, null)); |
|||
} else |
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.mention_card)); |
|||
} else if (currentPM.getAuthorColor() == ParseHelpers.USER_COLOR_PINK) { |
|||
//Special card for special member of the month!
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.member_of_the_month_card, null)); |
|||
} else |
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.member_of_the_month_card)); |
|||
} else holder.cardChildLinear.setBackground(null); |
|||
|
|||
//Avoid's view's visibility recycling
|
|||
if (inboxViewModel.isUserExtraInfoVisible(holder.getAdapterPosition())) { |
|||
holder.userExtraInfo.setVisibility(View.VISIBLE); |
|||
holder.userExtraInfo.setAlpha(1.0f); |
|||
|
|||
holder.username.setMaxLines(Integer.MAX_VALUE); |
|||
holder.username.setEllipsize(null); |
|||
|
|||
holder.subject.setTextColor(Color.parseColor("#FFFFFF")); |
|||
holder.subject.setMaxLines(Integer.MAX_VALUE); |
|||
holder.subject.setEllipsize(null); |
|||
} else { |
|||
holder.userExtraInfo.setVisibility(View.GONE); |
|||
holder.userExtraInfo.setAlpha(0.0f); |
|||
|
|||
holder.username.setMaxLines(1); |
|||
holder.username.setEllipsize(TextUtils.TruncateAt.END); |
|||
|
|||
holder.subject.setTextColor(Color.parseColor("#757575")); |
|||
holder.subject.setMaxLines(1); |
|||
holder.subject.setEllipsize(TextUtils.TruncateAt.END); |
|||
} |
|||
|
|||
//Sets graphics behavior
|
|||
holder.thumbnail.setOnClickListener(view -> { |
|||
//Clicking the thumbnail opens user's profile
|
|||
Intent intent = new Intent(context, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, currentPM.getAuthorProfileUrl()); |
|||
if (currentPM.getThumbnailUrl() == null) |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); |
|||
else |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPM.getAuthorProfileUrl()); |
|||
extras.putString(BUNDLE_PROFILE_USERNAME, currentPM.getAuthor()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
}); |
|||
holder.header.setOnClickListener(v -> { |
|||
//Clicking the header makes it expand/collapse
|
|||
inboxViewModel.toggleUserInfo(holder.getAdapterPosition()); |
|||
MessageAnimations.animateUserExtraInfoVisibility(holder.username, |
|||
holder.subject, Color.parseColor("#FFFFFF"), |
|||
Color.parseColor("#757575"), holder.userExtraInfo); |
|||
}); |
|||
//Clicking the expanded part of a header (the extra info) makes it collapse
|
|||
holder.userExtraInfo.setOnClickListener(v -> { |
|||
inboxViewModel.hideUserInfo(holder.getAdapterPosition()); |
|||
MessageAnimations.animateUserExtraInfoVisibility(holder.username, |
|||
holder.subject, Color.parseColor("#FFFFFF"), |
|||
Color.parseColor("#757575"), (LinearLayout) v); |
|||
}); |
|||
|
|||
holder.overflowButton.setOnClickListener(view -> { |
|||
LayoutInflater layoutInflater = LayoutInflater.from(context); |
|||
View popupContent = layoutInflater.inflate(R.layout.activity_inbox_overflow_menu, null); |
|||
|
|||
//Creates the PopupWindow
|
|||
final PopupWindow popUp = new PopupWindow(holder.overflowButton.getContext()); |
|||
popUp.setContentView(popupContent); |
|||
popUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT); |
|||
popUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT); |
|||
popUp.setFocusable(true); |
|||
|
|||
TextView quoteButton = popupContent.findViewById(R.id.pm_quote_button); |
|||
quoteButton.setVisibility(View.GONE); // TODO
|
|||
Drawable quoteDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_format_quote); |
|||
quoteButton.setCompoundDrawablesRelativeWithIntrinsicBounds(quoteDrawable, null, null, null); |
|||
quoteButton.setOnClickListener(v -> { |
|||
Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show(); |
|||
// TODO: Create delete PM task
|
|||
}); |
|||
|
|||
final TextView replyButton = popupContent.findViewById(R.id.pm_reply_button); |
|||
Drawable replyDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_reply); |
|||
replyButton.setCompoundDrawablesRelativeWithIntrinsicBounds(replyDrawable, null, null, null); |
|||
|
|||
replyButton.setOnClickListener(v -> { |
|||
Intent sendPMIntent = new Intent(context, CreatePMActivity.class); |
|||
sendPMIntent.putExtra(CreatePMActivity.BUNDLE_SEND_PM_URL, currentPM.getReplyUrl()); |
|||
context.startActivity(sendPMIntent); |
|||
popUp.dismiss(); |
|||
}); |
|||
|
|||
TextView deletePostButton = popupContent.findViewById(R.id.delete_post); |
|||
|
|||
Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp); |
|||
deletePostButton.setVisibility(View.GONE); //TODO
|
|||
deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null); |
|||
popupContent.findViewById(R.id.delete_post).setOnClickListener(v -> { |
|||
new AlertDialog.Builder(holder.overflowButton.getContext()) |
|||
.setTitle("Delete personal message") |
|||
.setMessage("Do you really want to delete this personal message?") |
|||
.setIcon(android.R.drawable.ic_dialog_alert) |
|||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { |
|||
Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show(); |
|||
// TODO: Create delete PM task
|
|||
}) |
|||
.setNegativeButton(android.R.string.no, null).show(); |
|||
popUp.dismiss(); |
|||
}); |
|||
|
|||
//Displays the popup
|
|||
popUp.showAsDropDown(holder.overflowButton); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return inboxViewModel.getInbox() == null ? 0 : pms().size(); |
|||
} |
|||
|
|||
private ArrayList<PM> pms() { |
|||
return inboxViewModel.getInbox().getPms(); |
|||
} |
|||
|
|||
static class PMViewHolder extends RecyclerView.ViewHolder { |
|||
final LinearLayout cardChildLinear; |
|||
final TextView pmDate, username, subject; |
|||
final ImageView thumbnail; |
|||
final public WebView pm; |
|||
final ImageButton overflowButton; |
|||
final RelativeLayout header; |
|||
final LinearLayout userExtraInfo; |
|||
|
|||
final TextView specialRank, rank, gender, numberOfPosts, personalText, stars; |
|||
|
|||
PMViewHolder(@NonNull View view) { |
|||
super(view); |
|||
cardChildLinear = view.findViewById(R.id.card_child_linear); |
|||
pmDate = view.findViewById(R.id.pm_date); |
|||
thumbnail = view.findViewById(R.id.thumbnail); |
|||
username = view.findViewById(R.id.username); |
|||
subject = view.findViewById(R.id.subject); |
|||
pm = view.findViewById(R.id.pm); |
|||
pm.setBackgroundColor(Color.argb(1, 255, 255, 255)); |
|||
overflowButton = view.findViewById(R.id.pm_overflow_menu); |
|||
|
|||
//User's extra info
|
|||
header = view.findViewById(R.id.header); |
|||
userExtraInfo = view.findViewById(R.id.user_extra_info); |
|||
specialRank = view.findViewById(R.id.special_rank); |
|||
rank = view.findViewById(R.id.rank); |
|||
gender = view.findViewById(R.id.gender); |
|||
numberOfPosts = view.findViewById(R.id.number_of_posts); |
|||
personalText = view.findViewById(R.id.personal_text); |
|||
stars = view.findViewById(R.id.stars); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This class is used to handle link clicks in WebViews. When link url is one that the app can |
|||
* handle internally, it does. Otherwise user is prompt to open the link in a browser. |
|||
*/ |
|||
private class LinkLauncher extends WebViewClient { |
|||
@Override |
|||
public boolean shouldOverrideUrlLoading(WebView view, String url) { |
|||
final Uri uri = Uri.parse(url); |
|||
return handleUri(uri); |
|||
} |
|||
|
|||
@TargetApi(Build.VERSION_CODES.N) |
|||
@Override |
|||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { |
|||
final Uri uri = request.getUrl(); |
|||
return handleUri(uri); |
|||
} |
|||
|
|||
@SuppressWarnings("SameReturnValue") |
|||
private boolean handleUri(final Uri uri) { |
|||
final String uriString = uri.toString(); |
|||
|
|||
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(uri); |
|||
if (target.is(ThmmyPage.PageCategory.TOPIC)) { |
|||
//This url points to a topic
|
|||
Intent intent = new Intent(context, TopicActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_TOPIC_URL, uriString); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
return true; |
|||
} else if (target.is(ThmmyPage.PageCategory.BOARD)) { |
|||
Intent intent = new Intent(context, BoardActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_BOARD_URL, uriString); |
|||
extras.putString(BUNDLE_BOARD_TITLE, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
return true; |
|||
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) { |
|||
Intent intent = new Intent(context, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, uriString); |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); |
|||
extras.putString(BUNDLE_PROFILE_USERNAME, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
return true; |
|||
} |
|||
|
|||
Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
|
|||
//Method always returns true as no url should be loaded in the WebViews
|
|||
return true; |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,203 @@ |
|||
package gr.thmmy.mthmmy.activities.inbox.tasks; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Inbox; |
|||
import gr.thmmy.mthmmy.model.PM; |
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import gr.thmmy.mthmmy.utils.parsing.NewParseTask; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseException; |
|||
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; |
|||
import okhttp3.Response; |
|||
|
|||
public class InboxTask extends NewParseTask<Inbox> { |
|||
@Override |
|||
protected Inbox parse(Document document, Response response) throws ParseException { |
|||
Inbox inbox = new Inbox(); |
|||
ParseHelpers.deobfuscateElements(document.select("span.__cf_email__,a.__cf_email__"), true); |
|||
|
|||
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); |
|||
|
|||
inbox.setCurrentPageIndex(ParseHelpers.parseCurrentPageIndexInbox(document, language)); |
|||
inbox.setNumberOfPages(ParseHelpers.parseNumberOfPagesInbox(document, inbox.getCurrentPageIndex(), language)); |
|||
|
|||
ArrayList<PM> pmList = parsePMs(document, language); |
|||
inbox.setPms(pmList); |
|||
return inbox; |
|||
} |
|||
|
|||
@Override |
|||
protected int getResultCode(Response response, Inbox data) { |
|||
return NetworkResultCodes.SUCCESSFUL; |
|||
} |
|||
|
|||
private ArrayList<PM> parsePMs(Document document, ParseHelpers.Language language) { |
|||
ArrayList<PM> pms = new ArrayList<>(); |
|||
Elements pmContainerContainers = document.select("td[style=padding: 1px 1px 0 1px;]"); |
|||
for (Element pmContainerContainer : pmContainerContainers) { |
|||
PM pm = new PM(); |
|||
boolean isAuthorDeleted; |
|||
Element pmContainer = pmContainerContainer.select("table[style=table-layout: fixed;]").first().child(0); |
|||
|
|||
Element thumbnail = pmContainer.select("img.avatar").first(); |
|||
// User might not have an avatar
|
|||
if (thumbnail != null) |
|||
pm.setThumbnailUrl(thumbnail.attr("src")); |
|||
|
|||
Element subjectAndDateContainer = pmContainer.select("td[align=left]").first(); |
|||
pm.setSubject(subjectAndDateContainer.select("b").first().text()); |
|||
Element dateContainer = subjectAndDateContainer.select("div").first(); |
|||
pm.setPmDate(subjectAndDateContainer.select("div").first().text()); |
|||
|
|||
String content = ParseHelpers.youtubeEmbeddedFix(pmContainer.select("div.personalmessage").first()); |
|||
//Adds stuff to make it work in WebView
|
|||
//style.css
|
|||
content = "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + content; |
|||
pm.setContent(content); |
|||
|
|||
pm.setQuoteUrl(pmContainer.select("img[src=https://www.thmmy.gr/smf/Themes/scribbles2_114/images/buttons/quote.gif]") |
|||
.first().parent().attr("href")); |
|||
pm.setReplyUrl(pmContainer.select("img[src=https://www.thmmy.gr/smf/Themes/scribbles2_114/images/buttons/im_reply.gif]") |
|||
.first().parent().attr("href")); |
|||
pm.setDeleteUrl(pmContainer.select("img[src=https://www.thmmy.gr/smf/Themes/scribbles2_114/images/buttons/delete.gif]") |
|||
.first().parent().attr("href")); |
|||
|
|||
// language specific parsing
|
|||
Element username; |
|||
if (language == ParseHelpers.Language.GREEK) { |
|||
//Finds username and profile's url
|
|||
username = pmContainer.select("a[title^=Εμφάνιση προφίλ του μέλους]").first(); |
|||
if (username == null) { //Deleted profile
|
|||
isAuthorDeleted = true; |
|||
String authorName = pmContainer.select("td:has(div.smalltext:containsOwn(Επισκέπτης))[style^=overflow]") |
|||
.first().text(); |
|||
authorName = authorName.substring(0, authorName.indexOf(" Επισκέπτης")); |
|||
pm.setAuthor(authorName); |
|||
pm.setAuthorColor(ParseHelpers.USER_COLOR_YELLOW); |
|||
} else { |
|||
isAuthorDeleted = false; |
|||
pm.setAuthor(username.html()); |
|||
pm.setAuthorProfileUrl(username.attr("href")); |
|||
} |
|||
|
|||
String date = dateContainer.text(); |
|||
date = date.substring(date.indexOf("στις:") + 6, date.indexOf(" »")); |
|||
pm.setPmDate(date); |
|||
} else { |
|||
//Finds username
|
|||
username = pmContainer.select("a[title^=View the profile of]").first(); |
|||
if (username == null) { //Deleted profile
|
|||
isAuthorDeleted = true; |
|||
String authorName = pmContainer |
|||
.select("td:has(div.smalltext:containsOwn(Guest))[style^=overflow]") |
|||
.first().text(); |
|||
authorName = authorName.substring(0, authorName.indexOf(" Guest")); |
|||
pm.setAuthor(authorName); |
|||
pm.setAuthorColor(ParseHelpers.USER_COLOR_YELLOW); |
|||
} else { |
|||
isAuthorDeleted = false; |
|||
pm.setAuthor(username.html()); |
|||
pm.setAuthorProfileUrl(username.attr("href")); |
|||
} |
|||
|
|||
String date = dateContainer.text(); |
|||
date = date.substring(date.indexOf("on:") + 4, date.indexOf(" »")); |
|||
pm.setPmDate(date); |
|||
} |
|||
|
|||
if (!isAuthorDeleted) { |
|||
int postsLineIndex = -1; |
|||
int starsLineIndex = -1; |
|||
|
|||
Element authorInfoContainer = pmContainer.select("div.smalltext").first(); |
|||
List<String> infoList = Arrays.asList(authorInfoContainer.html().split("<br>")); |
|||
|
|||
if (language == ParseHelpers.Language.GREEK) { |
|||
for (String line : infoList) { |
|||
if (line.contains("Μηνύματα:")) { |
|||
postsLineIndex = infoList.indexOf(line); |
|||
//Remove any line breaks and spaces on the start and end
|
|||
pm.setAuthorNumberOfPosts(line.replace("\n", "").replace("\r", "").trim()); |
|||
} |
|||
if (line.contains("Φύλο:")) { |
|||
if (line.contains("alt=\"Άντρας\"")) |
|||
pm.setAuthorGender("Φύλο: Άντρας"); |
|||
else |
|||
pm.setAuthorGender("Φύλο: Γυναίκα"); |
|||
} |
|||
if (line.contains("alt=\"*\"")) { |
|||
starsLineIndex = infoList.indexOf(line); |
|||
Document starsHtml = Jsoup.parse(line); |
|||
pm.setAuthorNumberOfStars(starsHtml.select("img[alt]").size()); |
|||
pm.setAuthorColor(ParseHelpers.colorPicker(starsHtml.select("img[alt]").first() |
|||
.attr("abs:src"))); |
|||
} |
|||
} |
|||
} else { |
|||
for (String line : infoList) { |
|||
if (line.contains("Posts:")) { |
|||
postsLineIndex = infoList.indexOf(line); |
|||
//Remove any line breaks and spaces on the start and end
|
|||
pm.setAuthorNumberOfPosts(line.replace("\n", "").replace("\r", "").trim()); |
|||
} |
|||
if (line.contains("Gender:")) { |
|||
if (line.contains("alt=\"Male\"")) |
|||
pm.setAuthorGender("Gender: Male"); |
|||
else |
|||
pm.setAuthorGender("Gender: Female"); |
|||
} |
|||
if (line.contains("alt=\"*\"")) { |
|||
starsLineIndex = infoList.indexOf(line); |
|||
Document starsHtml = Jsoup.parse(line); |
|||
pm.setAuthorNumberOfStars(starsHtml.select("img[alt]").size()); |
|||
pm.setAuthorColor(ParseHelpers.colorPicker(starsHtml.select("img[alt]").first() |
|||
.attr("abs:src"))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//If this member has no stars yet ==> New member,
|
|||
//or is just a member
|
|||
if (starsLineIndex == -1 || starsLineIndex == 1) { |
|||
pm.setAuthorRank(infoList.get(0).trim()); //First line has the rank
|
|||
//They don't have a special rank
|
|||
} else if (starsLineIndex == 2) { //This member has a special rank
|
|||
pm.setAuthorSpecialRank(infoList.get(0).trim());//First line has the special rank
|
|||
pm.setAuthorRank(infoList.get(1).trim());//Second line has the rank
|
|||
} |
|||
for (int i = postsLineIndex + 1; i < infoList.size() - 1; ++i) { |
|||
//Searches under "Posts:"
|
|||
//and above "Personal Message", "View Profile" etc buttons
|
|||
String thisLine = infoList.get(i); |
|||
if (!Objects.equals(thisLine, "") && thisLine != null |
|||
&& !Objects.equals(thisLine, " \n") |
|||
&& !thisLine.contains("avatar") |
|||
&& !thisLine.contains("<a href=")) { |
|||
String personalText = thisLine; |
|||
personalText = personalText.replace("\n", "").replace("\r", "").trim(); |
|||
pm.setAuthorPersonalText(personalText); |
|||
} |
|||
} |
|||
|
|||
//Checks post for mentions of this user (if the user is logged in)
|
|||
if (BaseActivity.getSessionManager().isLoggedIn() && |
|||
ParseHelpers.mentionsPattern.matcher(pm.getContent()).find()) { |
|||
pm.setUserMentioned(true); |
|||
} |
|||
} |
|||
pms.add(pm); |
|||
} |
|||
return pms; |
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,32 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
public class Inbox { |
|||
private ArrayList<PM> pms; |
|||
private int currentPageIndex, numberOfPages; |
|||
|
|||
public int getCurrentPageIndex() { |
|||
return currentPageIndex; |
|||
} |
|||
|
|||
public void setCurrentPageIndex(int currentPageIndex) { |
|||
this.currentPageIndex = currentPageIndex; |
|||
} |
|||
|
|||
public int getNumberOfPages() { |
|||
return numberOfPages; |
|||
} |
|||
|
|||
public void setNumberOfPages(int numberOfPages) { |
|||
this.numberOfPages = numberOfPages; |
|||
} |
|||
|
|||
public ArrayList<PM> getPms() { |
|||
return pms; |
|||
} |
|||
|
|||
public void setPms(ArrayList<PM> pms) { |
|||
this.pms = pms; |
|||
} |
|||
} |
@ -0,0 +1,157 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import gr.thmmy.mthmmy.utils.parsing.StringUtils; |
|||
import gr.thmmy.mthmmy.utils.parsing.ThmmyDateTimeParser; |
|||
|
|||
public class PM { |
|||
|
|||
private String thumbnailUrl; |
|||
private String author; |
|||
private String authorProfileUrl; |
|||
private String subject; |
|||
private String content; |
|||
private String pmDate; |
|||
private String deleteUrl, replyUrl, quoteUrl; |
|||
private int authorColor; |
|||
private String authorGender; |
|||
private String authorNumberOfPosts; |
|||
private String authorRank, authorSpecialRank, authorPersonalText; |
|||
private int authorNumberOfStars; |
|||
private boolean isUserMentioned; |
|||
|
|||
public int getAuthorColor() { |
|||
return authorColor; |
|||
} |
|||
|
|||
public String getAuthorPersonalText() { |
|||
return authorPersonalText; |
|||
} |
|||
|
|||
public void setAuthorPersonalText(String authorPersonalText) { |
|||
this.authorPersonalText = authorPersonalText; |
|||
} |
|||
|
|||
public String getAuthorNumberOfPosts() { |
|||
return authorNumberOfPosts; |
|||
} |
|||
|
|||
public String getAuthorRank() { |
|||
return authorRank; |
|||
} |
|||
|
|||
public void setAuthorRank(String rank) { |
|||
this.authorRank = rank; |
|||
} |
|||
|
|||
public String getAuthorSpecialRank() { |
|||
return authorSpecialRank; |
|||
} |
|||
|
|||
public void setAuthorSpecialRank(String authorSpecialRank) { |
|||
this.authorSpecialRank = authorSpecialRank; |
|||
} |
|||
|
|||
public String getAuthorGender() { |
|||
return authorGender; |
|||
} |
|||
|
|||
public void setAuthorGender(String authorGender) { |
|||
this.authorGender = authorGender; |
|||
} |
|||
|
|||
public int getAuthorNumberOfStars() { |
|||
return authorNumberOfStars; |
|||
} |
|||
|
|||
public void setAuthorNumberOfStars(int authorNumberOfStars) { |
|||
this.authorNumberOfStars = authorNumberOfStars; |
|||
} |
|||
|
|||
public void setAuthorNumberOfPosts(String authorNumberOfPosts) { |
|||
this.authorNumberOfPosts = authorNumberOfPosts; |
|||
} |
|||
|
|||
public String getReplyUrl() { |
|||
return replyUrl; |
|||
} |
|||
|
|||
public void setReplyUrl(String replyUrl) { |
|||
this.replyUrl = replyUrl; |
|||
} |
|||
|
|||
public String getQuoteUrl() { |
|||
return quoteUrl; |
|||
} |
|||
|
|||
public void setQuoteUrl(String quoteUrl) { |
|||
this.quoteUrl = quoteUrl; |
|||
} |
|||
|
|||
public String getDeleteUrl() { |
|||
return deleteUrl; |
|||
} |
|||
|
|||
public void setDeleteUrl(String deleteUrl) { |
|||
this.deleteUrl = deleteUrl; |
|||
} |
|||
|
|||
public void setAuthorColor(int authorColor) { |
|||
this.authorColor = authorColor; |
|||
} |
|||
|
|||
public boolean isUserMentioned() { |
|||
return isUserMentioned; |
|||
} |
|||
|
|||
public void setUserMentioned(boolean userMentioned) { |
|||
isUserMentioned = userMentioned; |
|||
} |
|||
|
|||
public String getAuthorProfileUrl() { |
|||
return authorProfileUrl; |
|||
} |
|||
|
|||
public void setAuthorProfileUrl(String authorProfileUrl) { |
|||
this.authorProfileUrl = authorProfileUrl; |
|||
} |
|||
|
|||
public void setThumbnailUrl(String thumbnailUrl) { |
|||
this.thumbnailUrl = thumbnailUrl; |
|||
} |
|||
|
|||
public void setAuthor(String author) { |
|||
this.author = author; |
|||
} |
|||
|
|||
public void setSubject(String subject) { |
|||
this.subject = subject; |
|||
} |
|||
|
|||
public void setContent(String content) { |
|||
this.content = content; |
|||
} |
|||
|
|||
public void setPmDate(String pmDate) { |
|||
this.pmDate = pmDate; |
|||
} |
|||
|
|||
public String getAuthor() { |
|||
return author; |
|||
} |
|||
|
|||
public String getContent() { |
|||
return content; |
|||
} |
|||
|
|||
public String getSubject() { |
|||
return subject; |
|||
} |
|||
|
|||
public String getThumbnailUrl() { |
|||
return thumbnailUrl; |
|||
} |
|||
|
|||
public String getPmDate() { |
|||
return pmDate; |
|||
} |
|||
} |
@ -0,0 +1,263 @@ |
|||
package gr.thmmy.mthmmy.pagination; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.graphics.Rect; |
|||
import android.os.Handler; |
|||
import android.util.AttributeSet; |
|||
import android.view.LayoutInflater; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.widget.ImageButton; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
|
|||
public class BottomPaginationView extends LinearLayout { |
|||
|
|||
/** |
|||
* Holds the initial time delay before a click on bottom navigation bar is considered long |
|||
*/ |
|||
private final long INITIAL_DELAY = 500; |
|||
private boolean autoIncrement = false; |
|||
private boolean autoDecrement = false; |
|||
/** |
|||
* Holds the number of pages to be added or subtracted from current page on each step while a |
|||
* long click is held in either next or previous buttons |
|||
*/ |
|||
private static final int SMALL_STEP = 1; |
|||
/** |
|||
* Holds the number of pages to be added or subtracted from current page on each step while a |
|||
* long click is held in either first or last buttons |
|||
*/ |
|||
private static final int LARGE_STEP = 10; |
|||
/** |
|||
* Used for handling bottom navigation bar's buttons long click user interactions |
|||
*/ |
|||
private final Handler repeatUpdateHandler = new Handler(); |
|||
|
|||
private ImageButton firstPage, previousPage, nextPage, lastPage; |
|||
private TextView pageIndicator; |
|||
|
|||
private int onDownPageIndex, indicatorPageIndex, totalPageCount; |
|||
|
|||
private OnPageRequestedListener onPageRequestedListener; |
|||
|
|||
public interface OnPageRequestedListener { |
|||
void onPageRequested(int index); |
|||
} |
|||
|
|||
public BottomPaginationView(Context context) { |
|||
this(context, null); |
|||
} |
|||
|
|||
public BottomPaginationView(Context context, @Nullable AttributeSet attrs) { |
|||
super(context, attrs); |
|||
|
|||
LayoutInflater inflater = LayoutInflater.from(context); |
|||
inflater.inflate(R.layout.pagination, this, true); |
|||
|
|||
firstPage = findViewById(R.id.page_first_button); |
|||
previousPage = findViewById(R.id.page_previous_button); |
|||
pageIndicator = findViewById(R.id.page_indicator); |
|||
nextPage = findViewById(R.id.page_next_button); |
|||
lastPage = findViewById(R.id.page_last_button); |
|||
|
|||
initDecrementButton(firstPage, LARGE_STEP); |
|||
initDecrementButton(previousPage, SMALL_STEP); |
|||
initIncrementButton(nextPage, SMALL_STEP); |
|||
initIncrementButton(lastPage, LARGE_STEP); |
|||
} |
|||
|
|||
public void setOnPageRequestedListener(OnPageRequestedListener onPageRequestedListener) { |
|||
this.onPageRequestedListener = onPageRequestedListener; |
|||
} |
|||
|
|||
public boolean setIndicatedPageIndex(int index) { |
|||
if (index != indicatorPageIndex) { |
|||
this.indicatorPageIndex = index; |
|||
updateUI(); |
|||
return true; |
|||
} else return false; |
|||
} |
|||
|
|||
public void setTotalPageCount(int totalPageCount) { |
|||
this.totalPageCount = totalPageCount; |
|||
updateUI(); |
|||
} |
|||
|
|||
public void updateUI() { |
|||
pageIndicator.setText(indicatorPageIndex + "/" + totalPageCount); |
|||
} |
|||
|
|||
/** |
|||
* This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue |
|||
* of page value when long pressing one of the page navigation buttons. |
|||
*/ |
|||
private class RepetitiveUpdater implements Runnable { |
|||
private final int step; |
|||
|
|||
/** |
|||
* @param step number of pages to add/subtract on each repetition |
|||
*/ |
|||
RepetitiveUpdater(int step) { |
|||
this.step = step; |
|||
} |
|||
|
|||
public void run() { |
|||
long REPEAT_DELAY = 250; |
|||
if (autoIncrement) { |
|||
incrementPageIndicator(step); |
|||
repeatUpdateHandler.postDelayed(new BottomPaginationView.RepetitiveUpdater(step), REPEAT_DELAY); |
|||
} else if (autoDecrement) { |
|||
decrementPageIndicator(step); |
|||
repeatUpdateHandler.postDelayed(new BottomPaginationView.RepetitiveUpdater(step), REPEAT_DELAY); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public boolean incrementPageIndicator(int step) { |
|||
int oldIndicatorIndex = indicatorPageIndex; |
|||
if (oldIndicatorIndex <= totalPageCount - step) |
|||
setIndicatedPageIndex(indicatorPageIndex + step); |
|||
else |
|||
setIndicatedPageIndex(totalPageCount); |
|||
return oldIndicatorIndex != indicatorPageIndex; |
|||
} |
|||
|
|||
public boolean decrementPageIndicator(int step) { |
|||
int oldIndicatorIndex = indicatorPageIndex; |
|||
if (oldIndicatorIndex > step) |
|||
setIndicatedPageIndex(indicatorPageIndex - step); |
|||
else |
|||
setIndicatedPageIndex(1); |
|||
return oldIndicatorIndex != indicatorPageIndex; |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
private void initIncrementButton(ImageButton increment, final int step) { |
|||
// Increment once for a click
|
|||
increment.setOnClickListener(v -> { |
|||
if (!autoIncrement && step == LARGE_STEP) { |
|||
boolean indicatorChanged = setIndicatedPageIndex(totalPageCount); |
|||
if (indicatorChanged) onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} else if (!autoIncrement) { |
|||
boolean indicatorChanged = incrementPageIndicator(1); |
|||
if (indicatorChanged) onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} |
|||
}); |
|||
|
|||
// Auto increment for a long click
|
|||
increment.setOnLongClickListener( |
|||
arg0 -> { |
|||
paginationDisable(arg0); |
|||
autoIncrement = true; |
|||
repeatUpdateHandler.postDelayed(new BottomPaginationView.RepetitiveUpdater(step), INITIAL_DELAY); |
|||
return false; |
|||
} |
|||
); |
|||
|
|||
// When the button is released
|
|||
increment.setOnTouchListener(new View.OnTouchListener() { |
|||
private Rect rect; |
|||
|
|||
public boolean onTouch(View v, MotionEvent event) { |
|||
if (event.getAction() == MotionEvent.ACTION_DOWN) { |
|||
onDownPageIndex = indicatorPageIndex; |
|||
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); |
|||
} else if (rect != null && event.getAction() == MotionEvent.ACTION_UP && autoIncrement) { |
|||
autoIncrement = false; |
|||
paginationEnabled(true); |
|||
onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} else if (rect != null && event.getAction() == MotionEvent.ACTION_MOVE) { |
|||
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { |
|||
autoIncrement = false; |
|||
setIndicatedPageIndex(onDownPageIndex); |
|||
paginationEnabled(true); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
private void initDecrementButton(ImageButton decrement, final int step) { |
|||
// Decrement once for a click
|
|||
decrement.setOnClickListener(v -> { |
|||
if (!autoDecrement && step == LARGE_STEP) { |
|||
boolean indicatorChanged = setIndicatedPageIndex(1); |
|||
if (indicatorChanged) onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} else if (!autoDecrement) { |
|||
boolean indicatorChanged = decrementPageIndicator(1); |
|||
if (indicatorChanged) onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} |
|||
}); |
|||
|
|||
// Auto decrement for a long click
|
|||
decrement.setOnLongClickListener( |
|||
arg0 -> { |
|||
paginationDisable(arg0); |
|||
autoDecrement = true; |
|||
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); |
|||
return false; |
|||
} |
|||
); |
|||
|
|||
// When the button is released
|
|||
decrement.setOnTouchListener(new View.OnTouchListener() { |
|||
private Rect rect; |
|||
|
|||
public boolean onTouch(View v, MotionEvent event) { |
|||
if (event.getAction() == MotionEvent.ACTION_DOWN) { |
|||
onDownPageIndex = indicatorPageIndex; |
|||
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); |
|||
} else if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) { |
|||
autoDecrement = false; |
|||
paginationEnabled(true); |
|||
onPageRequestedListener.onPageRequested(indicatorPageIndex); |
|||
} else if (event.getAction() == MotionEvent.ACTION_MOVE) { |
|||
if (rect != null && |
|||
!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { |
|||
autoIncrement = false; |
|||
setIndicatedPageIndex(onDownPageIndex); |
|||
paginationEnabled(true); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void paginationDisable(View exception) { |
|||
if (exception == firstPage) { |
|||
previousPage.setEnabled(false); |
|||
nextPage.setEnabled(false); |
|||
lastPage.setEnabled(false); |
|||
} else if (exception == previousPage) { |
|||
firstPage.setEnabled(false); |
|||
nextPage.setEnabled(false); |
|||
lastPage.setEnabled(false); |
|||
} else if (exception == nextPage) { |
|||
firstPage.setEnabled(false); |
|||
previousPage.setEnabled(false); |
|||
lastPage.setEnabled(false); |
|||
} else if (exception == lastPage) { |
|||
firstPage.setEnabled(false); |
|||
previousPage.setEnabled(false); |
|||
nextPage.setEnabled(false); |
|||
} else { |
|||
paginationEnabled(false); |
|||
} |
|||
} |
|||
|
|||
public void paginationEnabled(boolean enabled) { |
|||
firstPage.setEnabled(enabled); |
|||
previousPage.setEnabled(enabled); |
|||
nextPage.setEnabled(enabled); |
|||
lastPage.setEnabled(enabled); |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
package gr.thmmy.mthmmy.utils.parsing; |
|||
|
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public class StringUtils { |
|||
/** |
|||
* Method that extracts the base URL from a topic's page URL. For example a topic with url similar to |
|||
* "https://www.thmmy.gr/smf/index.php?topic=1.15;topicseen" or |
|||
* "https://www.thmmy.gr/smf/index.php?topic=1.msg1#msg1" |
|||
* has the base url "https://www.thmmy.gr/smf/index.php?topic=1" |
|||
* |
|||
* @param topicURL a topic's page URL |
|||
* @return the base URL of the given topic |
|||
*/ |
|||
public static String getBaseURL(String topicURL) { |
|||
String forumUrl = "https://www.thmmy.gr/smf/index.php?"; |
|||
Matcher baseUrlMatcher = Pattern.compile("topic=[0-9]+").matcher(topicURL); |
|||
if (baseUrlMatcher.find()) |
|||
return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end()); |
|||
else return ""; |
|||
} |
|||
|
|||
public static int extractUserCodeFromUrl(String url) { |
|||
Matcher userCodeMatcher = Pattern.compile("u=[0-9]+").matcher(url); |
|||
if (userCodeMatcher.find()) |
|||
return Integer.parseInt(url.substring(userCodeMatcher.start()+2, userCodeMatcher.end())); |
|||
else return -1; |
|||
} |
|||
} |
@ -0,0 +1,116 @@ |
|||
package gr.thmmy.mthmmy.viewmodel; |
|||
|
|||
import android.os.AsyncTask; |
|||
|
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.ViewModel; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.activities.inbox.tasks.InboxTask; |
|||
import gr.thmmy.mthmmy.model.Inbox; |
|||
import gr.thmmy.mthmmy.model.PM; |
|||
import gr.thmmy.mthmmy.pagination.BottomPaginationView; |
|||
import gr.thmmy.mthmmy.utils.ExternalAsyncTask; |
|||
import gr.thmmy.mthmmy.utils.NetworkResultCodes; |
|||
import timber.log.Timber; |
|||
|
|||
public class InboxViewModel extends ViewModel implements InboxTask.OnNetworkTaskFinishedListener<Inbox>, |
|||
BottomPaginationView.OnPageRequestedListener { |
|||
private static final String INBOX_URL = "https://www.thmmy.gr/smf/index.php?action=pm"; |
|||
/** |
|||
* caches the expand/collapse state of the user extra info in the current page for the recyclerview |
|||
*/ |
|||
private ArrayList<Boolean> userExtraInfoVisibile = new ArrayList<>(); |
|||
|
|||
private MutableLiveData<Integer> pageIndicatorIndex = new MutableLiveData<>(); |
|||
private MutableLiveData<Integer> pageCount = new MutableLiveData<>(); |
|||
|
|||
private InboxTask currentInboxTask; |
|||
|
|||
private Inbox inbox; |
|||
private InboxTask.OnNetworkTaskFinishedListener<Inbox> onInboxTaskFinishedListener; |
|||
private InboxTask.OnTaskStartedListener onInboxTaskStartedListener; |
|||
private InboxTask.OnTaskCancelledListener onInboxTaskCancelledListener; |
|||
|
|||
public void loadInbox() { |
|||
loadUrl(INBOX_URL); |
|||
} |
|||
|
|||
public void loadInboxPage(int index) { |
|||
loadUrl(INBOX_URL + ";f=inbox;sort=date;start=" + 15*(index-1)); |
|||
} |
|||
|
|||
public void loadUrl(String url) { |
|||
stopLoading(); |
|||
currentInboxTask = new InboxTask(); |
|||
currentInboxTask.setOnTaskStartedListener(onInboxTaskStartedListener); |
|||
currentInboxTask.setOnNetworkTaskFinishedListener(this); |
|||
currentInboxTask.setOnTaskCancelledListener(onInboxTaskCancelledListener); |
|||
currentInboxTask.execute(url); |
|||
} |
|||
|
|||
public void stopLoading() { |
|||
if (currentInboxTask != null && currentInboxTask.getStatus() == AsyncTask.Status.RUNNING) { |
|||
Timber.i("Canceling inbox task"); |
|||
currentInboxTask.cancel(true); |
|||
onInboxTaskCancelledListener.onTaskCanceled(); |
|||
} |
|||
} |
|||
|
|||
public void setOnInboxTaskFinishedListener(InboxTask.OnNetworkTaskFinishedListener<Inbox> onInboxTaskFinishedListener) { |
|||
this.onInboxTaskFinishedListener = onInboxTaskFinishedListener; |
|||
} |
|||
|
|||
public void setOnInboxTaskStartedListener(InboxTask.OnTaskStartedListener onInboxTaskStartedListener) { |
|||
this.onInboxTaskStartedListener = onInboxTaskStartedListener; |
|||
} |
|||
|
|||
@Override |
|||
public void onPageRequested(int index) { |
|||
pageIndicatorIndex.setValue(index); |
|||
loadInboxPage(index); |
|||
} |
|||
|
|||
@Override |
|||
public void onNetworkTaskFinished(int resultCode, Inbox inbox) { |
|||
this.inbox = inbox; |
|||
onInboxTaskFinishedListener.onNetworkTaskFinished(resultCode, inbox); |
|||
if (resultCode == NetworkResultCodes.SUCCESSFUL) { |
|||
userExtraInfoVisibile.clear(); |
|||
for (PM pm : inbox.getPms()) |
|||
userExtraInfoVisibile.add(false); |
|||
|
|||
pageIndicatorIndex.setValue(inbox.getCurrentPageIndex()); |
|||
pageCount.setValue(inbox.getNumberOfPages()); |
|||
} |
|||
} |
|||
|
|||
public void setOnInboxTaskCancelledListener(InboxTask.OnTaskCancelledListener onInboxTaskCancelledListener) { |
|||
this.onInboxTaskCancelledListener = onInboxTaskCancelledListener; |
|||
} |
|||
|
|||
public Inbox getInbox() { |
|||
return inbox; |
|||
} |
|||
|
|||
public MutableLiveData<Integer> getPageCount() { |
|||
return pageCount; |
|||
} |
|||
|
|||
public MutableLiveData<Integer> getPageIndicatorIndex() { |
|||
return pageIndicatorIndex; |
|||
} |
|||
|
|||
public boolean isUserExtraInfoVisible(int position) { |
|||
return userExtraInfoVisibile.get(position); |
|||
} |
|||
|
|||
public void hideUserInfo(int position) { |
|||
userExtraInfoVisibile.set(position, false); |
|||
} |
|||
|
|||
public void toggleUserInfo(int position) { |
|||
userExtraInfoVisibile.set(position, !userExtraInfoVisibile.get(position)); |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
<vector android:height="24dp" android:tint="#FFFFFF" |
|||
android:viewportHeight="24.0" android:viewportWidth="24.0" |
|||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> |
|||
</vector> |
@ -0,0 +1,56 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.create_pm.CreatePMActivity"> |
|||
|
|||
<RelativeLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
<com.google.android.material.appbar.AppBarLayout |
|||
android:id="@+id/appbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingTop="@dimen/appbar_padding_top" |
|||
android:theme="@style/ToolbarTheme"> |
|||
|
|||
<androidx.appcompat.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
android:gravity="center" |
|||
app:popupTheme="@style/ToolbarTheme"></androidx.appcompat.widget.Toolbar> |
|||
</com.google.android.material.appbar.AppBarLayout> |
|||
|
|||
<include |
|||
layout="@layout/full_post_editor" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@id/appbar" /> |
|||
|
|||
<gr.thmmy.mthmmy.editorview.EmojiKeyboard |
|||
android:id="@+id/emoji_keyboard" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentBottom="true" |
|||
android:visibility="gone" /> |
|||
</RelativeLayout> |
|||
|
|||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar |
|||
android:id="@+id/progressBar" |
|||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="@dimen/progress_bar_height" |
|||
android:indeterminate="true" |
|||
android:visibility="invisible" |
|||
app:layout_anchor="@id/appbar" |
|||
app:layout_anchorGravity="bottom|center" |
|||
app:mpb_indeterminateTint="@color/accent" |
|||
app:mpb_progressStyle="horizontal" /> |
|||
|
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
@ -0,0 +1,71 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:orientation="vertical" |
|||
tools:context=".activities.inbox.InboxActivity"> |
|||
|
|||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar |
|||
android:id="@+id/progress_bar" |
|||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="@dimen/progress_bar_height" |
|||
android:indeterminate="true" |
|||
android:visibility="invisible" |
|||
app:layout_anchor="@id/appbar" |
|||
app:layout_anchorGravity="bottom|center" |
|||
app:mpb_indeterminateTint="@color/accent" |
|||
app:mpb_progressStyle="horizontal" /> |
|||
|
|||
<RelativeLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
<com.google.android.material.appbar.AppBarLayout |
|||
android:id="@+id/appbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:theme="@style/ToolbarTheme"> |
|||
|
|||
<androidx.appcompat.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/colorPrimary" |
|||
android:gravity="center" |
|||
app:popupTheme="@style/ToolbarTheme" /> |
|||
</com.google.android.material.appbar.AppBarLayout> |
|||
|
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/inbox_recyclerview" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@id/appbar" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior" |
|||
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity" |
|||
tools:listitem="@layout/activity_inbox_pm_row" /> |
|||
</RelativeLayout> |
|||
|
|||
<gr.thmmy.mthmmy.pagination.BottomPaginationView |
|||
android:id="@+id/bottom_pagination" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="50dp" |
|||
android:layout_gravity="bottom|end" |
|||
android:background="@color/primary" |
|||
app:elevation="8dp" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareLinearBehavior"/> |
|||
|
|||
<!-- |
|||
<com.google.android.material.floatingactionbutton.FloatingActionButton |
|||
android:id="@+id/send_pm_fab" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="bottom|end" |
|||
android:layout_marginBottom="50dp" |
|||
android:layout_marginEnd="@dimen/fab_margins" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" |
|||
app:srcCompat="@drawable/ic_message_white_24dp" /> |
|||
--> |
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
@ -0,0 +1,49 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical"> |
|||
|
|||
<TextView |
|||
android:id="@+id/pm_quote_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="35dp" |
|||
android:background="?android:attr/selectableItemBackground" |
|||
android:drawablePadding="5dp" |
|||
android:gravity="center_vertical" |
|||
android:paddingBottom="6dp" |
|||
android:paddingEnd="12dp" |
|||
android:paddingStart="12dp" |
|||
android:paddingTop="6dp" |
|||
android:text="@string/quote_menu_button_text" |
|||
android:textColor="@color/primary_text" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/pm_reply_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="35dp" |
|||
android:background="?android:attr/selectableItemBackground" |
|||
android:drawablePadding="5dp" |
|||
android:gravity="center_vertical" |
|||
android:paddingBottom="6dp" |
|||
android:paddingEnd="12dp" |
|||
android:paddingStart="12dp" |
|||
android:paddingTop="6dp" |
|||
android:text="@string/reply_menu_button_text" |
|||
android:textColor="@color/primary_text" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/delete_post" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="35dp" |
|||
android:background="?android:attr/selectableItemBackground" |
|||
android:drawablePadding="5dp" |
|||
android:gravity="center_vertical" |
|||
android:paddingBottom="6dp" |
|||
android:paddingEnd="12dp" |
|||
android:paddingStart="12dp" |
|||
android:paddingTop="6dp" |
|||
android:text="@string/post_delete_button" |
|||
android:textColor="@color/primary_text" /> |
|||
|
|||
</LinearLayout> |
@ -0,0 +1,212 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:paddingEnd="4dp" |
|||
android:paddingStart="4dp" |
|||
tools:ignore="SmallSp"> |
|||
|
|||
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" |
|||
android:id="@+id/card_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="center" |
|||
android:foreground="?android:attr/selectableItemBackground" |
|||
card_view:cardBackgroundColor="@color/card_background" |
|||
card_view:cardCornerRadius="5dp" |
|||
card_view:cardElevation="2dp" |
|||
card_view:cardPreventCornerOverlap="false" |
|||
card_view:cardUseCompatPadding="true"> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/card_child_linear" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:orientation="horizontal"> |
|||
|
|||
<RelativeLayout |
|||
android:id="@+id/header" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp" |
|||
android:paddingTop="16dp"> |
|||
|
|||
<FrameLayout |
|||
android:id="@+id/thumbnail_holder" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:layout_centerVertical="true" |
|||
android:layout_marginEnd="16dp"> |
|||
|
|||
<ImageView |
|||
android:id="@+id/thumbnail" |
|||
android:layout_width="@dimen/thumbnail_size" |
|||
android:layout_height="@dimen/thumbnail_size" |
|||
android:layout_gravity="center" |
|||
android:adjustViewBounds="true" |
|||
android:contentDescription="@string/personal_message_thumbnail" |
|||
android:transitionName="user_thumbnail" |
|||
app:srcCompat="@drawable/ic_default_user_avatar_darker" /> |
|||
</FrameLayout> |
|||
|
|||
<TextView |
|||
android:id="@+id/username" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:layout_toEndOf="@+id/thumbnail_holder" |
|||
android:ellipsize="end" |
|||
android:maxLines="1" |
|||
tools:text="@string/pm_author" |
|||
android:textColor="@color/primary_text" |
|||
android:textStyle="bold" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/subject" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@+id/username" |
|||
android:layout_toEndOf="@+id/thumbnail_holder" |
|||
android:ellipsize="end" |
|||
android:maxLines="1" |
|||
tools:text="@string/pm_subject" /> |
|||
</RelativeLayout> |
|||
<ImageButton |
|||
android:id="@+id/pm_overflow_menu" |
|||
android:layout_width="@dimen/post_image_button" |
|||
android:layout_height="@dimen/post_image_button" |
|||
android:layout_marginTop="9dp" |
|||
android:layout_marginEnd="9dp" |
|||
android:background="@color/card_background" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/post_overflow_menu_button" |
|||
android:focusable="true" |
|||
app:srcCompat="@drawable/ic_more_vert_white_24dp" /> |
|||
</LinearLayout> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/user_extra_info" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
android:orientation="vertical" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp" |
|||
android:paddingTop="3dp" |
|||
android:visibility="gone" |
|||
android:weightSum="1.0"> |
|||
|
|||
<TextView |
|||
android:id="@+id/special_rank" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/rank" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/stars" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="2dp" |
|||
android:textSize="10sp" |
|||
android:visibility="gone" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/gender" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/number_of_posts" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/personal_text" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:textStyle="italic" |
|||
android:visibility="gone" /> |
|||
|
|||
</LinearLayout> |
|||
|
|||
<FrameLayout |
|||
android:id="@+id/pm_date_exp" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:layout_marginTop="16dp"> |
|||
|
|||
<TextView |
|||
android:id="@+id/pm_date" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:gravity="start" |
|||
android:text="" |
|||
android:textColor="@color/accent" |
|||
android:textSize="11sp" /> |
|||
</FrameLayout> |
|||
|
|||
|
|||
<View |
|||
android:id="@+id/header_body_divider" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="1dp" |
|||
android:layout_marginBottom="9dp" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:layout_marginTop="5dp" |
|||
android:background="@color/divider" /> |
|||
|
|||
<FrameLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<WebView |
|||
android:id="@+id/pm" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:background="@color/card_background" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
tools:text="@string/pm_content" /> |
|||
</FrameLayout> |
|||
</LinearLayout> |
|||
</androidx.cardview.widget.CardView> |
|||
</LinearLayout> |
@ -0,0 +1,32 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:orientation="vertical" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto"> |
|||
|
|||
<com.google.android.material.textfield.TextInputLayout |
|||
android:id="@+id/subject_input" |
|||
android:layout_width="240dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_margin="16dp" |
|||
android:hint="@string/subject"> |
|||
|
|||
<com.google.android.material.textfield.TextInputEditText |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:lines="1" |
|||
android:maxLines="1" |
|||
android:ellipsize="end" |
|||
android:inputType="text"/> |
|||
</com.google.android.material.textfield.TextInputLayout> |
|||
|
|||
<gr.thmmy.mthmmy.editorview.EditorView |
|||
android:id="@+id/main_content_editorview" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginStart="16dp" |
|||
android:layout_marginEnd="16dp" |
|||
app:hint="topic message"/> |
|||
|
|||
</LinearLayout> |
@ -0,0 +1,54 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<merge xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
android:background="@color/primary" |
|||
app:elevation="8dp" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareLinearBehavior"> |
|||
<ImageButton |
|||
android:id="@+id/page_first_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="match_parent" |
|||
android:layout_weight="0.8" |
|||
android:background="?attr/selectableItemBackgroundBorderless" |
|||
android:contentDescription="@string/button_first" |
|||
app:srcCompat="@drawable/page_first" /> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/page_previous_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="match_parent" |
|||
android:layout_weight="0.8" |
|||
android:background="?attr/selectableItemBackgroundBorderless" |
|||
android:contentDescription="@string/button_previous" |
|||
app:srcCompat="@drawable/page_previous" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/page_indicator" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="center" |
|||
android:layout_weight="1" |
|||
android:gravity="center" |
|||
android:hint="@string/button_page" |
|||
android:maxLines="1" |
|||
android:textColor="@color/white" |
|||
android:textSize="22sp" /> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/page_next_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="match_parent" |
|||
android:layout_weight="0.8" |
|||
android:background="?attr/selectableItemBackgroundBorderless" |
|||
android:contentDescription="@string/button_next" |
|||
app:srcCompat="@drawable/page_next" /> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/page_last_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="match_parent" |
|||
android:layout_weight="0.8" |
|||
android:background="?attr/selectableItemBackgroundBorderless" |
|||
android:contentDescription="@string/button_last" |
|||
app:srcCompat="@drawable/page_last" /> |
|||
</merge> |
@ -0,0 +1,19 @@ |
|||
package gr.thmmy.mthmmy.utils.parsing; |
|||
|
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
|
|||
import static org.junit.Assert.*; |
|||
|
|||
public class StringUtilsTest { |
|||
|
|||
@Before |
|||
public void setUp() throws Exception { |
|||
} |
|||
|
|||
@Test |
|||
public void extractUserCodeFromUrl() { |
|||
String url = "https://www.thmmy.gr/smf/index.php?action=profile;u=14670"; |
|||
assertEquals(StringUtils.extractUserCodeFromUrl(url), 14670); |
|||
} |
|||
} |
@ -1,6 +1,6 @@ |
|||
#Tue Sep 17 12:32:34 EEST 2019 |
|||
#Mon Mar 30 15:17:37 EEST 2020 |
|||
distributionBase=GRADLE_USER_HOME |
|||
distributionPath=wrapper/dists |
|||
zipStoreBase=GRADLE_USER_HOME |
|||
zipStorePath=wrapper/dists |
|||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip |
|||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip |
|||
|
Loading…
Reference in new issue