diff --git a/app/build.gradle b/app/build.gradle index 764cd232..522d12b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.0" + defaultConfig { applicationId "gr.thmmy.mthmmy" - minSdkVersion 15 + minSdkVersion 16 targetSdkVersion 25 versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + versionName "0.12" } buildTypes { release { @@ -21,9 +21,17 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) compile 'com.android.support:appcompat-v7:25.0.0' - testCompile 'junit:junit:4.12' + compile 'com.android.support:design:25.0.0' + compile 'com.squareup.okhttp3:okhttp:3.4.1' + compile 'org.jsoup:jsoup:1.10.1' + compile 'com.android.support:support-v4:25.0.0' + compile 'com.android.support:cardview-v7:25.0.0' + compile 'com.android.support:recyclerview-v7:25.0.0' + compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0' + compile('com.mikepenz:materialdrawer:5.7.0@aar') { + transitive = true + } + + } diff --git a/app/src/androidTest/java/gr/thmmy/mthmmy/ExampleInstrumentedTest.java b/app/src/androidTest/java/gr/thmmy/mthmmy/ExampleInstrumentedTest.java deleted file mode 100644 index 7c6fd486..00000000 --- a/app/src/androidTest/java/gr/thmmy/mthmmy/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package gr.thmmy.mthmmy; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("gr.thmmy.mthmmy", appContext.getPackageName()); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7517b415..f2f51291 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,13 +1,44 @@ + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java new file mode 100644 index 00000000..2c31a71a --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java @@ -0,0 +1,23 @@ +package gr.thmmy.mthmmy.activities; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.widget.TextView; + +import gr.thmmy.mthmmy.BuildConfig; +import gr.thmmy.mthmmy.R; + +public class AboutActivity extends BaseActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + String versionName = BuildConfig.VERSION_NAME; + TextView tv= (TextView) findViewById(R.id.version); + if(tv!=null) + tv.setText(getString(R.string.version, versionName)); + + //TODO: add licenses + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/BaseActivity.java new file mode 100644 index 00000000..f06396fd --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/BaseActivity.java @@ -0,0 +1,39 @@ +package gr.thmmy.mthmmy.activities; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.franmontiel.persistentcookiejar.PersistentCookieJar; +import com.franmontiel.persistentcookiejar.cache.SetCookieCache; +import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; + +import okhttp3.CookieJar; + +public class BaseActivity extends AppCompatActivity { + + private static boolean cookiesInit=false; //To initialize cookie stuff only once per app start + protected static CookieJar cookieJar; + protected static SharedPrefsCookiePersistor sharedPrefsCookiePersistor; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if(!cookiesInit) + { + sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(BaseActivity.this); + cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); + cookiesInit=true; + } + + } + + public static CookieJar getCookieJar() + { + return cookieJar; + } + + public static SharedPrefsCookiePersistor getSharedPrefsCookiePersistor() { + return sharedPrefsCookiePersistor; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/MainActivity.java new file mode 100644 index 00000000..b7e53aee --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/MainActivity.java @@ -0,0 +1,115 @@ +package gr.thmmy.mthmmy.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.data.TopicSummary; +import gr.thmmy.mthmmy.sections.recent.RecentFragment; + +public class MainActivity extends BaseActivity implements RecentFragment.OnListFragmentInteractionListener +{ + private SectionsPagerAdapter mSectionsPagerAdapter; + private ViewPager mViewPager; /** The {@link ViewPager} that will host the section contents.*/ + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + // Create the adapter that will return a fragment for each section of the activity + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) findViewById(R.id.container); + mViewPager.setAdapter(mSectionsPagerAdapter); + + TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); + tabLayout.setupWithViewPager(mViewPager); + + //TODO: Drawer +// new DrawerBuilder().withActivity(this) +// .withToolbar(toolbar) +// .build(); + + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + int id = item.getItemId(); + + if (id == R.id.action_about) { + Intent i = new Intent(MainActivity.this, AboutActivity.class); + startActivity(i); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onFragmentInteraction(TopicSummary topicSummary) + { + Intent i = new Intent(MainActivity.this, TopicActivity.class); + i.putExtra("TOPIC_URL", topicSummary.getTopicUrl()); + i.putExtra("TOPIC_TITLE", topicSummary.getTitle()); + startActivity(i); + } + +//---------------------------------FragmentPagerAdapter--------------------------------------------- + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. If it becomes too memory intensive, + * it may be best to switch to a + * {@link android.support.v4.app.FragmentStatePagerAdapter}. + */ + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + return RecentFragment.newInstance(position + 1); + } + + @Override + public int getCount() { + // Show 1 total pages. + return 1; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "RECENT"; + } + return null; + } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/TopicActivity.java new file mode 100644 index 00000000..003ec965 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/TopicActivity.java @@ -0,0 +1,181 @@ +package gr.thmmy.mthmmy.activities; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.data.Post; +import gr.thmmy.mthmmy.utils.CustomRecyclerView; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.SSLHandshakeException; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class TopicActivity extends BaseActivity { + + private CustomRecyclerView recyclerView; + private TopicAdapter topicAdapter; + private ProgressBar progressBar; + private OkHttpClient client; + + private List postsList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_topic); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + + + Bundle extras = getIntent().getExtras(); + ActionBar actionbar = getSupportActionBar(); + if(actionbar!=null) + actionbar.setTitle(extras.getString("TOPIC_TITLE")); + + postsList = new ArrayList<>(); + + topicAdapter = new TopicAdapter(); + + recyclerView = (CustomRecyclerView) findViewById(R.id.list); + recyclerView.setLayoutManager(new LinearLayoutManager(findViewById(R.id.list).getContext())); + recyclerView.setAdapter(topicAdapter); + + client = new OkHttpClient.Builder().build(); + new TopicTask().execute(extras.getString("TOPIC_URL")); + + } + + + private class TopicAdapter extends RecyclerView.Adapter + { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.activity_topic_post_row, parent, false); + return new ViewHolder(view); + } + + + + @Override + public void onBindViewHolder(final ViewHolder holder, final int position) { + + holder.mAuthorView.setText(postsList.get(position).getAuthor()); + holder.mDateTimeView.setText(postsList.get(position).getDateTime()); + holder.mContentView.loadData(postsList.get(position).getContent(), "text/html", null); + +// holder.topic = recentList.get(position); +// +// holder.mView.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// +// if (null != mListener) { +// // Notify the active callbacks interface (the activity, if the +// // fragment is attached to one) that an item has been selected. +// mListener.onFragmentInteraction(holder.topic); //? +// +// } +// +// } +// }); + } + + @Override + public int getItemCount() { + return postsList.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public final View mView; + public final TextView mAuthorView; + public final WebView mContentView; + public final TextView mDateTimeView; + + public ViewHolder(View view) { + super(view); + mView = view; + mAuthorView = (TextView) view.findViewById(R.id.author); + mContentView = (WebView) view.findViewById(R.id.content); + mDateTimeView = (TextView) view.findViewById(R.id.dateTime); + } + + } + } + + + +//---------------------------------------TOPIC ASYNC TASK------------------------------------------- + + public class TopicTask extends AsyncTask + { + private static final String TAG="TopicTask"; + private String pageLink; + + private Document document; + + + protected void onPreExecute() { + progressBar.setVisibility(ProgressBar.VISIBLE); + } + + protected Boolean doInBackground(String... strings) + { + pageLink = strings[0]; + + Request request = new Request.Builder() + .url(pageLink) + .build(); + + try { + Response response = client.newCall(request).execute(); + document = Jsoup.parse(response.body().string()); + return parse(document); + } catch (SSLHandshakeException e) { + Log.w(TAG, "Certificate problem (please switch to unsafe connection)."); + return false; + + } catch (Exception e) { + Log.e("TAG", "ERROR", e); + return false; + } + + } + + + + protected void onPostExecute(Boolean result) + { + progressBar.setVisibility(ProgressBar.INVISIBLE); + topicAdapter.notifyDataSetChanged(); + } + + private boolean parse(Document document) + { + return true; + + } + + + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/data/Post.java b/app/src/main/java/gr/thmmy/mthmmy/data/Post.java new file mode 100644 index 00000000..c7eb6bee --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/data/Post.java @@ -0,0 +1,29 @@ +package gr.thmmy.mthmmy.data; + +/** + * Created by ezero on 14/9/2016. + */ +public class Post +{ + private final String author; + private final String dateTime; + private String content; + + public Post(String author, String dateTime, String content) { + this.author = author; + this.dateTime = dateTime; + this.content = content; + } + + public String getContent() { + return content; + } + + public String getAuthor() { + return author; + } + + public String getDateTime() { + return dateTime; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java b/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java new file mode 100644 index 00000000..0bb05c6d --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java @@ -0,0 +1,31 @@ +package gr.thmmy.mthmmy.data; + +public class TopicSummary +{ + private final String topicUrl; + private final String title; + private final String lastUser; + private final String dateTimeModified; + + + public TopicSummary(String topicUrl, String title, String lastUser, String dateTimeModified) + { + this.topicUrl = topicUrl; + this.title = title; + this.lastUser = lastUser; + this.dateTimeModified = dateTimeModified; + } + + public String getTopicUrl() { return topicUrl; } + public String getTitle() { + return title; + } + + public String getLastUser() { + return lastUser; + } + + public String getDateTimeModified() { + return dateTimeModified; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentAdapter.java new file mode 100644 index 00000000..4a542cc5 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentAdapter.java @@ -0,0 +1,88 @@ +package gr.thmmy.mthmmy.sections.recent; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.data.TopicSummary; + +import java.util.List; + + +/** + * {@link RecyclerView.Adapter} that can display a {@link TopicSummary} and makes a call to the + * specified {@link RecentFragment.OnListFragmentInteractionListener}. + */ +public class RecentAdapter extends RecyclerView.Adapter +{ + private final List recentList; + private final RecentFragment.OnListFragmentInteractionListener mListener; + + public RecentAdapter(List topicSummaryList, RecentFragment.OnListFragmentInteractionListener listener) { + this.recentList = topicSummaryList; + mListener = listener; + } + + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.fragment_recent_row, parent, false); + return new ViewHolder(view); + } + + + + @Override + public void onBindViewHolder(final ViewHolder holder, final int position) { + + holder.mTitleView.setText(recentList.get(position).getTitle()); + holder.mDateTimeView.setText(recentList.get(position).getDateTimeModified()); + holder.mUserView.setText("by " + recentList.get(position).getLastUser()); + + holder.topic = recentList.get(position); + + holder.mView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mListener.onFragmentInteraction(holder.topic); //? + + } + + } + }); + } + + @Override + public int getItemCount() { + return recentList.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public final View mView; + public final TextView mTitleView; + public final TextView mUserView; + public final TextView mDateTimeView; + public TopicSummary topic; + + public ViewHolder(View view) { + super(view); + mView = view; + mTitleView = (TextView) view.findViewById(R.id.title); + mUserView = (TextView) view.findViewById(R.id.lastUser); + mDateTimeView = (TextView) view.findViewById(R.id.dateTime); + } + +// @Override +// public String toString() { +// return super.toString() + " '" + mContentView.getText() + "'"; +// } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentFragment.java new file mode 100644 index 00000000..69f7e100 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/sections/recent/RecentFragment.java @@ -0,0 +1,363 @@ +package gr.thmmy.mthmmy.sections.recent; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.data.TopicSummary; +import gr.thmmy.mthmmy.utils.CustomRecyclerView; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link RecentFragment.OnListFragmentInteractionListener} interface + * to handle interaction events. + * Use the {@link RecentFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class RecentFragment extends Fragment +{ + private static final String TAG = "RecentFragment"; + // Fragment initialization parameters, e.g. ARG_SECTION_NUMBER + private static final String ARG_SECTION_NUMBER = "SectionNumber"; + private int sectionNumber; + + private ProgressBar progressBar; + private SwipeRefreshLayout swipeRefreshLayout; + private CustomRecyclerView recyclerView; + private RecentAdapter recentAdapter; + + private List topicSummaries; + + private OnListFragmentInteractionListener mListener; + + OkHttpClient client; + + // Required empty public constructor + public RecentFragment() {} + + /** + * Use ONLY this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param sectionNumber + * @return A new instance of fragment Recent. + */ + public static RecentFragment newInstance(int sectionNumber) + { + RecentFragment fragment = new RecentFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_SECTION_NUMBER, sectionNumber); + fragment.setArguments(args); + return fragment; + } + + public int getSectionNumber() { + return sectionNumber; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER); + + client = new OkHttpClient.Builder().build(); + + + topicSummaries = new ArrayList<>(); + + + if(sectionNumber==1) //? + Log.d(TAG,"onCreate"); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + if(sectionNumber==1)//temp + { + if(topicSummaries.isEmpty()) + new RecentTask().execute(); + + + } + Log.d(TAG,"onActivityCreated"); + } + + @Override + public void onStart() { + super.onStart(); + if(sectionNumber==1) + Log.d(TAG,"onStart"); + } + + @Override + public void onResume() { + super.onResume(); + if(sectionNumber==1) + Log.d(TAG,"onResume"); + } + + @Override + public void onPause() { + super.onPause(); + if(sectionNumber==1) + Log.d(TAG,"onPause"); + } + + @Override + public void onStop() { + super.onStop(); + if(sectionNumber==1) + Log.d(TAG,"onStop"); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + // Inflate the layout for this fragment + final View rootView = inflater.inflate(R.layout.fragment_recent, container, false); + + // Set the adapter + if (rootView instanceof RelativeLayout) + { + progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar); + recentAdapter = new RecentAdapter(topicSummaries, mListener); + + recyclerView = (CustomRecyclerView) rootView.findViewById(R.id.list); + recyclerView.setLayoutManager(new LinearLayoutManager(rootView.findViewById(R.id.list).getContext())); + recyclerView.setAdapter(recentAdapter); + + swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swiperefresh); + swipeRefreshLayout.setOnRefreshListener( + new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + new RecentTask().execute(); + + } + + } + ); + + + } + + return rootView; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnListFragmentInteractionListener) { + mListener = (OnListFragmentInteractionListener) context; + + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + */ + public interface OnListFragmentInteractionListener { + // TODO: Update argument type and name + void onFragmentInteraction(TopicSummary topicSummary); + } + + + int n=0; + long s=0; + + + //---------------------------------------ASYNC TASK----------------------------------- + + public class RecentTask extends AsyncTask + { + private static final String TAG="RecentTask"; + private final String thmmyUrl = "https://www.thmmy.gr/smf/index.php"; + + private Document document; + + + protected void onPreExecute() { + + progressBar.setVisibility(ProgressBar.VISIBLE); + } + + protected Integer doInBackground(Void... voids) + { + + Request request = new Request.Builder() + .url(thmmyUrl) + .build(); + try { + Response response = client.newCall(request).execute(); + document = Jsoup.parse(response.body().string()); + parse(document); + return 0; + } catch (IOException e) { + Log.d("DEB", "ERROR", e); + return 1; + } catch (Exception e) { + Log.d("DEB", "ERROR", e); + return 2; + } + + } + + + + protected void onPostExecute(Integer result) + { + + if(result==0) + recentAdapter.notifyDataSetChanged(); + else if (result==1) + Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); + + progressBar.setVisibility(ProgressBar.INVISIBLE); + swipeRefreshLayout.setRefreshing(false); + } + + private boolean parse(Document document) + { + Elements recent = document.select("#block8 :first-child div"); + if(recent.size()==30) + { + topicSummaries.clear(); + + for(int i=0; i0) + enableRefreshing=false; + super.onScrolled(dx, dy); + } + + + @Override + public void onScrollStateChanged(int state) + { + if((state!=SCROLL_STATE_DRAGGING)&&((LinearLayoutManager)getLayoutManager()).findFirstCompletelyVisibleItemPosition()==0) + enableRefreshing=true; + else if(getChildCount()==0) + enableRefreshing=true; + else if(((LinearLayoutManager)getLayoutManager()).findFirstCompletelyVisibleItemPosition()!=0) + enableRefreshing=false; + + + + super.onScrollStateChanged(state); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + if(enableRefreshing) + return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + else + return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, 0, offsetInWindow); + } + +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/Thmmy.java b/app/src/main/java/gr/thmmy/mthmmy/utils/Thmmy.java new file mode 100644 index 00000000..acabca99 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/Thmmy.java @@ -0,0 +1,309 @@ +package gr.thmmy.mthmmy.utils; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import com.franmontiel.persistentcookiejar.PersistentCookieJar; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.net.ssl.SSLHandshakeException; + +import gr.thmmy.mthmmy.activities.BaseActivity; +import okhttp3.Cookie; +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + + +public class Thmmy +{ + private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2"); + private static final HttpUrl indexUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php"); + + public static final int OK = 1; + public static final int WRONG_USER= 2; + public static final int WRONG_PASSWORD= 3; + public static final int FAILED= 4; + public static final int CERTIFICATE_ERROR = 5; + public static final int OTHER_ERROR = 6; + + + + + + public static int authenticate(String username, String password) + { + LoginData loginData = login(username, password, "60"); + + int status = loginData.getStatus(); + if (status==OK) + { + logout(loginData.getLogoutLink()); + return OK; + } + return status; + + } + + + //-------------------------------------------LOGIN-------------------------------------------------- + //Two options: (username, password, duration) or nothing - cookies + public static LoginData login(String... strings) + { + Log.d("Login","Logging in..."); + LoginData loginData = new LoginData(); + Request request; + + if(strings.length==3) + { + String loginName = strings[0]; + String password = strings[1]; + String duration = strings[2]; + + ((PersistentCookieJar) BaseActivity.getCookieJar()).clear(); + + RequestBody formBody = new FormBody.Builder() + .add("user", loginName) + .add("passwrd", password) + .add("cookielength", duration) //Forever is -1 + .build(); + request = new Request.Builder() + .url(loginUrl) + .post(formBody) + .build(); + } + else + { + request = new Request.Builder() + .url(loginUrl) + .build(); + } + + OkHttpClient client = new OkHttpClient.Builder() + .cookieJar(BaseActivity.getCookieJar()) + .build(); + + + try + { + Response response = client.newCall(request).execute(); + Document document = Jsoup.parse(response.body().string()); + + Element logout = document.getElementById("logoutbtn"); + + if (logout != null) + { + Log.i("Login", "Login successful"); + setPersistentCookieSession(); + loginData.setUsername(extractUserName(document)); + loginData.setLogoutLink(HttpUrl.parse(logout.attr("href"))); + loginData.setStatus(OK); + } + else + { + Log.w("Login", "Login failed"); + loginData.setStatus(FAILED); + + //Making error more specific + Elements error = document.select("b:contains(That username does not exist.)"); + + if (error.size()==1) + { + loginData.setStatus(WRONG_USER); + Log.d("Login","Wrong Username"); + } + + error = document.select("body:contains(Password incorrect)"); + if (error.size()==1) + { + Log.d("Login","Wrong Password"); + loginData.setStatus(WRONG_PASSWORD); + } + + ((PersistentCookieJar) BaseActivity.getCookieJar()).clear(); + + } + } catch (SSLHandshakeException e) { + Log.w("Login", "Certificate problem"); + loginData.setStatus(CERTIFICATE_ERROR); + + } catch (Exception e) { + Log.e("Login", "Error", e); + loginData.setStatus(OTHER_ERROR); + } + + + + return loginData; + + + } + + public static class LoginData implements Parcelable + { + private int status; + private String username; + private HttpUrl logoutLink; + + public LoginData() {} + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + + public HttpUrl getLogoutLink() { + return logoutLink; + } + + public void setLogoutLink(HttpUrl logoutLink) { + this.logoutLink = logoutLink; + } + + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeString(username); + out.writeString(logoutLink.toString()); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public LoginData createFromParcel(Parcel in) { + return new LoginData(in); + } + + public LoginData[] newArray(int size) { + return new LoginData[size]; + } + }; + + private LoginData(Parcel in) { + status = in.readInt(); + username=in.readString(); + logoutLink=HttpUrl.parse(in.readString()); + } + } + + private static boolean setPersistentCookieSession() + { + List cookieList = BaseActivity.getCookieJar().loadForRequest(HttpUrl.parse("https://www.thmmy.gr")); + + if (cookieList.size() == 2) { + if ((cookieList.get(0).name().equals("THMMYgrC00ki3")) && (cookieList.get(1).name().equals("PHPSESSID"))) + { + Cookie.Builder builder = new Cookie.Builder(); + builder.name(cookieList.get(1).name()) + .value(cookieList.get(1).value()) + .domain(cookieList.get(1).domain()) + .expiresAt(cookieList.get(0).expiresAt()); + cookieList.remove(1); + cookieList.add(builder.build()); + BaseActivity.getSharedPrefsCookiePersistor().clear(); + BaseActivity.getSharedPrefsCookiePersistor().saveAll(cookieList); + return true; + } + } + return false; + } + //-------------------------------------LOGIN ENDS----------------------------------------------- + + + + //--------------------------------------LOGOUT-------------------------------------------------- + //Two options: (username, password, duration) or nothing - cookies + public static int logout(HttpUrl logoutLink) + { + OkHttpClient client = new OkHttpClient.Builder() + .cookieJar(BaseActivity.getCookieJar()) //cookies will be deleted + .build(); + Request request = new Request.Builder() + .url(logoutLink) + .build(); + + try { + Response response = client.newCall(request).execute(); + Document document = Jsoup.parse(response.body().string()); + + Elements login = document.select("[value=Login]"); + if(!login.isEmpty()) + { + Log.i("Logout", "Logout successful"); + return OK; + } + else + { + Log.w("Logout", "Logout failed"); + return FAILED; + } + } catch (SSLHandshakeException e) { + Log.w("Logout", "Certificate problem (please switch to unsafe connection)."); + return CERTIFICATE_ERROR; + + } catch (Exception e) { + Log.d("Logout", "ERROR", e); + return OTHER_ERROR; + } + + + } + + + + +//----------------------------------------LOGOUT ENDS----------------------------------------------- + + + + +//-------------------------------------------MISC--------------------------------------------------- + public static String extractUserName(Document doc) + { + if(doc!=null) + { + Elements user = doc.select("div[id=myuser] > h3"); + + if (user.size()==1) + { + String txt = user.first().ownText(); + + Pattern pattern = Pattern.compile(", (.*?),"); + Matcher matcher = pattern.matcher(txt); + if (matcher.find()) + return matcher.group(1); + } + } + + return null; + } + +} diff --git a/app/src/main/res/drawable/row.xml b/app/src/main/res/drawable/row.xml new file mode 100644 index 00000000..8b48a759 --- /dev/null +++ b/app/src/main/res/drawable/row.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 00000000..9501a130 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..a471081e --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_topic.xml b/app/src/main/res/layout/activity_topic.xml new file mode 100644 index 00000000..248bef4b --- /dev/null +++ b/app/src/main/res/layout/activity_topic.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_topic_post_row.xml b/app/src/main/res/layout/activity_topic_post_row.xml new file mode 100644 index 00000000..d92c5ce8 --- /dev/null +++ b/app/src/main/res/layout/activity_topic_post_row.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recent.xml b/app/src/main/res/layout/fragment_recent.xml new file mode 100644 index 00000000..41ac000f --- /dev/null +++ b/app/src/main/res/layout/fragment_recent.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recent_row.xml b/app/src/main/res/layout/fragment_recent_row.xml new file mode 100644 index 00000000..3d9e0420 --- /dev/null +++ b/app/src/main/res/layout/fragment_recent_row.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 00000000..7a6c2d23 --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..dbbdd40f --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 00000000..63fc8164 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3ab3e9cb..f1a0b98c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,19 @@ - #3F51B5 - #303F9F - #FF4081 + #313335 + #2b2b2b + #616161 + #2b2b2b + + #00000000 + + #ffffff + #616161 + + #ffb74d + #1e88e5 + #f57f17 + #7e57c2 + #388e3c + #d32f2f diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..cef3abc4 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + 16dp + 16dp + 16dp + 8dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6c5c0d8..74f67206 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,11 @@ mTHMMY + Settings + Hello World from section: %1$d + + About + v%1$s + logo + Name + Login diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930d..ad3e53f3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,4 +8,20 @@ @color/colorAccent + + + + + + + diff --git a/app/src/test/java/gr/thmmy/mthmmy/ExampleUnitTest.java b/app/src/test/java/gr/thmmy/mthmmy/ExampleUnitTest.java deleted file mode 100644 index c3e8ebcd..00000000 --- a/app/src/test/java/gr/thmmy/mthmmy/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package gr.thmmy.mthmmy; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index c20bca14..01b0b9a0 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { repositories { jcenter() + maven { url "https://jitpack.io" } } dependencies { classpath 'com.android.tools.build:gradle:2.2.2' @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url "https://jitpack.io" } } }