@ -1 +1 @@ |
|||
1.0.0 |
|||
1.1.0 |
|||
|
@ -0,0 +1,142 @@ |
|||
package gr.thmmy.mthmmy.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.graphics.Typeface; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.board.BoardActivity; |
|||
import gr.thmmy.mthmmy.activities.topic.TopicActivity; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Bookmark; |
|||
|
|||
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.topic.TopicActivity.BUNDLE_TOPIC_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; |
|||
|
|||
public class BookmarkActivity extends BaseActivity { |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_bookmark); |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
toolbar.setTitle("Bookmarks"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(BOOKMARKS_ID); |
|||
|
|||
LinearLayout bookmarksLinearView = (LinearLayout) findViewById(R.id.bookmarks_container); |
|||
LayoutInflater layoutInflater = getLayoutInflater(); |
|||
|
|||
TextView tmp = new TextView(this); |
|||
tmp.setLayoutParams(new LinearLayout.LayoutParams( |
|||
LinearLayout.LayoutParams.MATCH_PARENT |
|||
, LinearLayout.LayoutParams.WRAP_CONTENT)); |
|||
tmp.setText(getString(R.string.board_bookmarks_title)); |
|||
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
tmp.setTextColor(getColor(R.color.primary_text)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
tmp.setTextColor(getResources().getColor(R.color.primary_text)); |
|||
} |
|||
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); |
|||
tmp.setTextSize(20f); |
|||
bookmarksLinearView.addView(tmp); |
|||
|
|||
for (final Bookmark bookmarkedBoard : getBoardsBookmarked()) { |
|||
if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) { |
|||
final LinearLayout row = (LinearLayout) layoutInflater.inflate( |
|||
R.layout.activity_bookmark_row, bookmarksLinearView, false); |
|||
row.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board=" |
|||
+ bookmarkedBoard.getId() + ".0"); |
|||
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle()); |
|||
intent.putExtras(extras); |
|||
startActivity(intent); |
|||
finish(); |
|||
} |
|||
}); |
|||
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle()); |
|||
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
removeBookmark(bookmarkedBoard); |
|||
row.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
bookmarksLinearView.addView(row); |
|||
} |
|||
} |
|||
|
|||
tmp = new TextView(this); |
|||
tmp.setLayoutParams(new LinearLayout.LayoutParams( |
|||
LinearLayout.LayoutParams.MATCH_PARENT |
|||
, LinearLayout.LayoutParams.WRAP_CONTENT)); |
|||
tmp.setText(getString(R.string.topic_bookmarks_title)); |
|||
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
tmp.setTextColor(getColor(R.color.primary_text)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
tmp.setTextColor(getResources().getColor(R.color.primary_text)); |
|||
} |
|||
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); |
|||
tmp.setTextSize(20f); |
|||
bookmarksLinearView.addView(tmp); |
|||
|
|||
for (final Bookmark bookmarkedTopic : getTopicsBookmarked()) { |
|||
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) { |
|||
final LinearLayout row = (LinearLayout) layoutInflater.inflate( |
|||
R.layout.activity_bookmark_row, bookmarksLinearView, false); |
|||
row.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" |
|||
+ bookmarkedTopic.getId() + ".0"); |
|||
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); |
|||
intent.putExtras(extras); |
|||
startActivity(intent); |
|||
finish(); |
|||
} |
|||
}); |
|||
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle()); |
|||
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
removeBookmark(bookmarkedTopic); |
|||
row.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
bookmarksLinearView.addView(row); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(BOOKMARKS_ID); |
|||
super.onResume(); |
|||
} |
|||
} |
@ -1,373 +0,0 @@ |
|||
package gr.thmmy.mthmmy.activities.base; |
|||
|
|||
import android.app.ProgressDialog; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v4.content.ContextCompat; |
|||
import android.support.v7.app.AppCompatActivity; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.franmontiel.persistentcookiejar.PersistentCookieJar; |
|||
import com.franmontiel.persistentcookiejar.cache.SetCookieCache; |
|||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; |
|||
import com.jakewharton.picasso.OkHttp3Downloader; |
|||
import com.mikepenz.fontawesome_typeface_library.FontAwesome; |
|||
import com.mikepenz.iconics.IconicsDrawable; |
|||
import com.mikepenz.materialdrawer.AccountHeader; |
|||
import com.mikepenz.materialdrawer.AccountHeaderBuilder; |
|||
import com.mikepenz.materialdrawer.Drawer; |
|||
import com.mikepenz.materialdrawer.DrawerBuilder; |
|||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IProfile; |
|||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader; |
|||
import com.mikepenz.materialdrawer.util.DrawerImageLoader; |
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.AboutActivity; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.main.MainActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import okhttp3.OkHttpClient; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME; |
|||
|
|||
public abstract class BaseActivity extends AppCompatActivity |
|||
{ |
|||
// Client & Cookies
|
|||
protected static OkHttpClient client; |
|||
private static final long connectTimeout = 30; //TimeUnit.SECONDS for all three
|
|||
private static final long writeTimeout = 30; |
|||
private static final long readTimeout = 30; |
|||
protected static Picasso picasso; |
|||
private static PersistentCookieJar cookieJar; |
|||
private static SharedPrefsCookiePersistor sharedPrefsCookiePersistor; |
|||
|
|||
//Shared Preferences
|
|||
protected static final String SHARED_PREFS_NAME = "ThmmySharedPrefs"; |
|||
protected static SharedPreferences sharedPrefs; |
|||
|
|||
//SessionManager
|
|||
protected static SessionManager sessionManager; |
|||
|
|||
//Other variables
|
|||
private static boolean init = false; //To initialize stuff only once per app start
|
|||
|
|||
//Common UI elements
|
|||
protected Toolbar toolbar; |
|||
protected Drawer drawer; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
|
|||
if (!init) { |
|||
sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); |
|||
sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(BaseActivity.this); |
|||
cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); |
|||
client = new OkHttpClient.Builder() |
|||
.cookieJar(cookieJar) |
|||
.connectTimeout(connectTimeout, TimeUnit.SECONDS) |
|||
.writeTimeout(writeTimeout, TimeUnit.SECONDS) |
|||
.readTimeout(readTimeout, TimeUnit.SECONDS) |
|||
.build(); |
|||
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs); |
|||
picasso = new Picasso.Builder(BaseActivity.this) |
|||
.downloader(new OkHttp3Downloader(client)) |
|||
.build(); |
|||
Picasso.setSingletonInstance(picasso); // all following Picasso (with Picasso.with(Context context) requests will use this Picasso object
|
|||
//initialize and create the image loader logic TODO move this to a singleton BaseApplication obj
|
|||
DrawerImageLoader.init(new AbstractDrawerImageLoader() { |
|||
@Override |
|||
public void set(ImageView imageView, Uri uri, Drawable placeholder) { |
|||
Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView); |
|||
} |
|||
@Override |
|||
public void cancel(ImageView imageView) { |
|||
Picasso.with(imageView.getContext()).cancelRequest(imageView); |
|||
} |
|||
|
|||
@Override |
|||
public Drawable placeholder(Context ctx, String tag) { |
|||
if (DrawerImageLoader.Tags.PROFILE.name().equals(tag)) { |
|||
return new IconicsDrawable(ctx).icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(ctx, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(ctx, R.color.primary)); |
|||
} |
|||
|
|||
return super.placeholder(ctx, tag); |
|||
} |
|||
}); |
|||
init = true; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
super.onResume(); |
|||
updateDrawer(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPause() { |
|||
super.onPause(); |
|||
if(drawer!=null) //close drawer animation after returning to activity
|
|||
drawer.closeDrawer(); |
|||
} |
|||
|
|||
|
|||
public static OkHttpClient getClient() |
|||
{ |
|||
return client; |
|||
} |
|||
|
|||
public static SessionManager getSessionManager() |
|||
{ |
|||
return sessionManager; |
|||
} |
|||
|
|||
//TODO: move stuff below
|
|||
//------------------------------------------DRAWER STUFF----------------------------------------
|
|||
protected static final int HOME_ID=0; |
|||
protected static final int LOG_ID =1; |
|||
protected static final int ABOUT_ID=2; |
|||
|
|||
private AccountHeader accountHeader; |
|||
private ProfileDrawerItem profileDrawerItem; |
|||
private PrimaryDrawerItem homeItem, loginLogoutItem, aboutItem; |
|||
private IconicsDrawable homeIcon, homeIconSelected, loginIcon, logoutIcon, |
|||
aboutIcon, aboutIconSelected; |
|||
|
|||
/** |
|||
* Call only after initializing Toolbar |
|||
*/ |
|||
protected void createDrawer() |
|||
{ |
|||
final int primaryColor = ContextCompat.getColor(this, R.color.iron); |
|||
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark); |
|||
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent); |
|||
|
|||
//Drawer Icons
|
|||
homeIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(primaryColor); |
|||
|
|||
homeIconSelected =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
loginIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_in) |
|||
.color(primaryColor); |
|||
|
|||
logoutIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_out) |
|||
.color(primaryColor); |
|||
|
|||
aboutIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(primaryColor); |
|||
|
|||
aboutIconSelected =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
//Drawer Items
|
|||
homeItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(HOME_ID) |
|||
.withName(R.string.home) |
|||
.withIcon(homeIcon) |
|||
.withSelectedIcon(homeIconSelected); |
|||
|
|||
if (!sessionManager.isLoggedIn()) //When logged out
|
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID).withName(R.string.login) |
|||
.withIcon(loginIcon) |
|||
.withSelectable(false); |
|||
else |
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID) |
|||
.withName(R.string.logout) |
|||
.withIcon(logoutIcon) |
|||
.withSelectable(false); |
|||
aboutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(ABOUT_ID) |
|||
.withName(R.string.about) |
|||
.withIcon(aboutIcon) |
|||
.withSelectedIcon(aboutIconSelected); |
|||
|
|||
//Profile
|
|||
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername()); |
|||
|
|||
//AccountHeader
|
|||
accountHeader = new AccountHeaderBuilder() |
|||
.withActivity(this) |
|||
.withCompactStyle(true) |
|||
.withSelectionListEnabledForSingleProfile(false) |
|||
.withHeaderBackground(R.color.primary) |
|||
.addProfiles(profileDrawerItem) |
|||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { |
|||
@Override |
|||
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { |
|||
if(sessionManager.isLoggedIn()) |
|||
{ |
|||
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile"); |
|||
if(!sessionManager.hasAvatar()) |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, ""); |
|||
else |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, sessionManager.getAvatarLink()); |
|||
extras.putString(BUNDLE_USERNAME, sessionManager.getUsername()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
startActivity(intent); |
|||
return false; |
|||
} |
|||
return true; |
|||
|
|||
} |
|||
}) |
|||
.build(); |
|||
|
|||
//Drawer
|
|||
drawer = new DrawerBuilder() |
|||
.withActivity(this) |
|||
.withToolbar(toolbar) |
|||
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.withAccountHeader(accountHeader) |
|||
.addDrawerItems(homeItem,loginLogoutItem,aboutItem) |
|||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { |
|||
@Override |
|||
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { |
|||
if(drawerItem.equals(HOME_ID)) |
|||
{ |
|||
if(!(BaseActivity.this instanceof MainActivity)) |
|||
{ |
|||
Intent i = new Intent(BaseActivity.this, MainActivity.class); |
|||
startActivity(i); |
|||
} |
|||
} |
|||
else if(drawerItem.equals(LOG_ID)) |
|||
{ |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
Intent intent = new Intent(BaseActivity.this, LoginActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); |
|||
} |
|||
else |
|||
new LogoutTask().execute(); |
|||
} |
|||
else if(drawerItem.equals(ABOUT_ID)) |
|||
{ |
|||
if(!(BaseActivity.this instanceof AboutActivity)) |
|||
{ |
|||
Intent i = new Intent(BaseActivity.this, AboutActivity.class); |
|||
startActivity(i); |
|||
} |
|||
|
|||
} |
|||
|
|||
drawer.closeDrawer(); |
|||
return true; |
|||
} |
|||
}) |
|||
.build(); |
|||
|
|||
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false); |
|||
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() { |
|||
@Override |
|||
public boolean onNavigationClickListener(View clickedView) { |
|||
onBackPressed(); |
|||
return true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected void updateDrawer() |
|||
{ |
|||
if(drawer!=null) |
|||
{ |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(this, R.color.primary))); |
|||
} |
|||
else |
|||
{ |
|||
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(sessionManager.getAvatarLink()); |
|||
} |
|||
accountHeader.updateProfile(profileDrawerItem); |
|||
drawer.updateItem(loginLogoutItem); |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
//-------------------------------------------LOGOUT-------------------------------------------------
|
|||
/** |
|||
* Result toast will always display a success, because when user chooses logout all data are |
|||
* cleared regardless of the actual outcome |
|||
*/ |
|||
protected class LogoutTask extends AsyncTask<Void, Void, Integer> { //Attempt logout
|
|||
ProgressDialog progressDialog; |
|||
|
|||
protected Integer doInBackground(Void... voids) { |
|||
return sessionManager.logout(); |
|||
} |
|||
|
|||
protected void onPreExecute() |
|||
{ //Show a progress dialog until done
|
|||
progressDialog = new ProgressDialog(BaseActivity.this, |
|||
R.style.AppTheme_Dark_Dialog); |
|||
progressDialog.setCancelable(false); |
|||
progressDialog.setIndeterminate(true); |
|||
progressDialog.setMessage("Logging out..."); |
|||
progressDialog.show(); |
|||
} |
|||
|
|||
protected void onPostExecute(Integer result) |
|||
{ |
|||
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show(); |
|||
updateDrawer(); |
|||
progressDialog.dismiss(); |
|||
} |
|||
} |
|||
//-----------------------------------------LOGOUT END-----------------------------------------------
|
|||
|
|||
|
|||
} |
@ -0,0 +1,279 @@ |
|||
package gr.thmmy.mthmmy.activities.downloads; |
|||
|
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.design.widget.FloatingActionButton; |
|||
import android.support.v7.widget.DividerItemDecoration; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.Toast; |
|||
|
|||
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.Objects; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Download; |
|||
import gr.thmmy.mthmmy.model.ThmmyPage; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "DownloadsActivity"; |
|||
/** |
|||
* The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_DOWNLOADS_URL = "DOWNLOADS_URL"; |
|||
/** |
|||
* The key to use when putting download's title String to {@link DownloadsActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_DOWNLOADS_TITLE = "DOWNLOADS_TITLE"; |
|||
private static final String downloadsIndexUrl = "https://www.thmmy.gr/smf/index.php?action=tpmod;dl;"; |
|||
private String downloadsUrl; |
|||
private String downloadsTitle; |
|||
private final ArrayList<Download> parsedDownloads = new ArrayList<>(); |
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private RecyclerView recyclerView; |
|||
private DownloadsAdapter downloadsAdapter; |
|||
private FloatingActionButton uploadFAB; |
|||
|
|||
private ParseDownloadPageTask parseDownloadPageTask; |
|||
private int numberOfPages = -1; |
|||
private int pagesLoaded = 0; |
|||
private boolean isLoadingMore; |
|||
private static final int visibleThreshold = 5; |
|||
private int lastVisibleItem, totalItemCount; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_downloads); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
downloadsTitle = extras.getString(BUNDLE_DOWNLOADS_TITLE); |
|||
downloadsUrl = extras.getString(BUNDLE_DOWNLOADS_URL); |
|||
if (downloadsUrl != null && !Objects.equals(downloadsUrl, "")) { |
|||
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(downloadsUrl)); |
|||
if (!target.is(ThmmyPage.PageCategory.DOWNLOADS)) { |
|||
Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + downloadsUrl); |
|||
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); |
|||
finish(); |
|||
} |
|||
} else downloadsUrl = downloadsIndexUrl; |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
if (downloadsTitle == null || Objects.equals(downloadsTitle, "")) |
|||
toolbar.setTitle("Downloads"); |
|||
toolbar.setTitle(downloadsTitle); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(DOWNLOADS_ID); |
|||
|
|||
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); |
|||
|
|||
recyclerView = (RecyclerView) findViewById(R.id.downloads_recycler_view); |
|||
recyclerView.setHasFixedSize(true); |
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); |
|||
recyclerView.setLayoutManager(layoutManager); |
|||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), |
|||
layoutManager.getOrientation()); |
|||
recyclerView.addItemDecoration(dividerItemDecoration); |
|||
downloadsAdapter = new DownloadsAdapter(getApplicationContext(), parsedDownloads); |
|||
recyclerView.setAdapter(downloadsAdapter); |
|||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { |
|||
@Override |
|||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
|||
super.onScrolled(recyclerView, dx, dy); |
|||
totalItemCount = layoutManager.getItemCount(); |
|||
lastVisibleItem = layoutManager.findLastVisibleItemPosition(); |
|||
|
|||
if (!isLoadingMore && totalItemCount <= (lastVisibleItem + visibleThreshold)) { |
|||
isLoadingMore = true; |
|||
onLoadMore(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
uploadFAB = (FloatingActionButton) findViewById(R.id.download_fab); |
|||
uploadFAB.setEnabled(false); |
|||
uploadFAB.hide(); |
|||
|
|||
parseDownloadPageTask = new ParseDownloadPageTask(); |
|||
parseDownloadPageTask.execute(downloadsUrl); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadMore() { |
|||
if (pagesLoaded < numberOfPages) { |
|||
parsedDownloads.add(null); |
|||
downloadsAdapter.notifyItemInserted(parsedDownloads.size()); |
|||
|
|||
//Load data
|
|||
parseDownloadPageTask = new ParseDownloadPageTask(); |
|||
if (downloadsUrl.contains("tpstart")) |
|||
parseDownloadPageTask.execute(downloadsUrl.substring(0 |
|||
, downloadsUrl.lastIndexOf(";tpstart=")) + ";tpstart=" + pagesLoaded * 10); |
|||
else parseDownloadPageTask.execute(downloadsUrl + ";tpstart=" + pagesLoaded * 10); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if (drawer.isDrawerOpen()) { |
|||
drawer.closeDrawer(); |
|||
return; |
|||
} |
|||
super.onBackPressed(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(DOWNLOADS_ID); |
|||
super.onResume(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDestroy() { |
|||
super.onDestroy(); |
|||
recyclerView.setAdapter(null); |
|||
if (parseDownloadPageTask != null && parseDownloadPageTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
parseDownloadPageTask.cancel(true); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of a downloads page and parsing it's |
|||
* data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link RecyclerView#swapAdapter} |
|||
* to build graphics. |
|||
* <p> |
|||
* <p>Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url |
|||
* as String parameter!</p> |
|||
*/ |
|||
class ParseDownloadPageTask extends AsyncTask<String, Void, Void> { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "ParseDownloadPageTask"; //Separate tag for AsyncTask
|
|||
private String thisPageUrl; |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false); |
|||
} |
|||
|
|||
@Override |
|||
protected Void doInBackground(String... downloadsUrl) { |
|||
thisPageUrl = downloadsUrl[0]; |
|||
Request request = new Request.Builder() |
|||
.url(downloadsUrl[0]) |
|||
.build(); |
|||
try { |
|||
Response response = BaseActivity.getClient().newCall(request).execute(); |
|||
parseDownloads(Jsoup.parse(response.body().string())); |
|||
} catch (SSLHandshakeException e) { |
|||
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); |
|||
} catch (Exception e) { |
|||
Report.e("TAG", "ERROR", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Void voids) { |
|||
if (downloadsTitle != null && !Objects.equals(downloadsTitle, "") && |
|||
toolbar.getTitle() != downloadsTitle) |
|||
toolbar.setTitle(downloadsTitle); |
|||
|
|||
++pagesLoaded; |
|||
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true); |
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
downloadsAdapter.notifyDataSetChanged(); |
|||
isLoadingMore = false; |
|||
} |
|||
|
|||
private void parseDownloads(Document downloadPage) { |
|||
if (downloadsTitle == null || Objects.equals(downloadsTitle, "")) |
|||
downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text(); |
|||
|
|||
//Removes loading item
|
|||
if (isLoadingMore) { |
|||
if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1); |
|||
} |
|||
|
|||
Download.DownloadItemType type; |
|||
if (ThmmyPage.resolvePageCategory(Uri.parse(thisPageUrl)).is(ThmmyPage. |
|||
PageCategory.DOWNLOADS_CATEGORY)) |
|||
type = Download.DownloadItemType.DOWNLOADS_CATEGORY; |
|||
else type = Download.DownloadItemType.DOWNLOADS_FILE; |
|||
|
|||
Elements pages = downloadPage.select("a.navPages"); |
|||
if (pages != null) { |
|||
for (Element page : pages) { |
|||
int pageNumber = Integer.parseInt(page.text()); |
|||
if (pageNumber > numberOfPages) numberOfPages = pageNumber; |
|||
} |
|||
} else numberOfPages = 1; |
|||
|
|||
Elements rows = downloadPage.select("table.tborder>tbody>tr"); |
|||
if (type == Download.DownloadItemType.DOWNLOADS_CATEGORY) { |
|||
Elements navigationLinks = downloadPage.select("div.nav>b"); |
|||
for (Element row : rows) { |
|||
if (row.select("td").size() == 1) continue; |
|||
|
|||
String url = row.select("b>a").first().attr("href"), |
|||
title = row.select("b>a").first().text(), |
|||
subtitle = row.select("div.smalltext:not(:has(a))").text(); |
|||
if (!row.select("td").last().hasClass("windowbg2")) { |
|||
if (navigationLinks.size() < 4) { |
|||
|
|||
parsedDownloads.add(new Download(type, url, title, subtitle, null, |
|||
true, null)); |
|||
} else { |
|||
String stats = row.text(); |
|||
stats = stats.replace(title, "").replace(subtitle, "").trim(); |
|||
parsedDownloads.add(new Download(type, url, title, subtitle, stats, |
|||
false, null)); |
|||
} |
|||
} else { |
|||
String stats = row.text(); |
|||
stats = stats.replace(title, "").replace(subtitle, "").trim(); |
|||
parsedDownloads.add(new Download(type, url, title, subtitle, stats, |
|||
false, null)); |
|||
} |
|||
} |
|||
} else { |
|||
parsedDownloads.add(new Download(type, |
|||
rows.select("b>a").first().attr("href"), |
|||
rows.select("b>a").first().text(), |
|||
rows.select("div.smalltext:not(:has(a))").text(), |
|||
rows.select("span:not(:has(a))").first().text(), |
|||
false, |
|||
rows.select("span:has(a)").first().text())); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,187 @@ |
|||
package gr.thmmy.mthmmy.activities.downloads; |
|||
|
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.graphics.Typeface; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageButton; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.model.Download; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_URL; |
|||
|
|||
class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "DownloadsAdapter"; |
|||
private final int VIEW_TYPE_DOWNLOAD = 0; |
|||
private final int VIEW_TYPE_LOADING = 1; |
|||
|
|||
private final Context context; |
|||
private ArrayList<Download> parsedDownloads = new ArrayList<>(); |
|||
private final ArrayList<Boolean> downloadExpandableVisibility = new ArrayList<>(); |
|||
|
|||
DownloadsAdapter(Context context, ArrayList<Download> parsedDownloads) { |
|||
this.context = context; |
|||
this.parsedDownloads = parsedDownloads; |
|||
} |
|||
|
|||
interface OnLoadMoreListener { |
|||
void onLoadMore(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(int position) { |
|||
return (parsedDownloads.get(position) == null) ? VIEW_TYPE_LOADING : VIEW_TYPE_DOWNLOAD; |
|||
} |
|||
|
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|||
if (viewType == VIEW_TYPE_DOWNLOAD) { |
|||
View download = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.activity_downloads_row, parent, false); |
|||
return new DownloadViewHolder(download); |
|||
} else if (viewType == VIEW_TYPE_LOADING) { |
|||
View loading = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.recycler_loading_item, parent, false); |
|||
return new LoadingViewHolder(loading); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { |
|||
if (holder instanceof DownloadViewHolder) { |
|||
final Download download = parsedDownloads.get(position); |
|||
final DownloadViewHolder downloadViewHolder = (DownloadViewHolder) holder; |
|||
|
|||
if (downloadExpandableVisibility.size() != parsedDownloads.size()) { |
|||
for (int i = downloadExpandableVisibility.size(); i < parsedDownloads.size(); ++i) |
|||
downloadExpandableVisibility.add(false); |
|||
} |
|||
|
|||
if (download.getType() == Download.DownloadItemType.DOWNLOADS_CATEGORY) { |
|||
downloadViewHolder.downloadRow.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(context, DownloadsActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_DOWNLOADS_URL, download.getUrl()); |
|||
extras.putString(BUNDLE_DOWNLOADS_TITLE, download.getTitle()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
}); |
|||
|
|||
if (downloadExpandableVisibility.get(downloadViewHolder.getAdapterPosition())) { |
|||
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE); |
|||
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} else { |
|||
downloadViewHolder.informationExpandable.setVisibility(View.GONE); |
|||
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} |
|||
downloadViewHolder.informationExpandableBtn.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
final boolean visible = downloadExpandableVisibility.get(downloadViewHolder. |
|||
getAdapterPosition()); |
|||
if (visible) { |
|||
downloadViewHolder.informationExpandable.setVisibility(View.GONE); |
|||
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} else { |
|||
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE); |
|||
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} |
|||
downloadExpandableVisibility.set(downloadViewHolder.getAdapterPosition(), !visible); |
|||
} |
|||
}); |
|||
downloadViewHolder.title.setTypeface(Typeface.createFromAsset(context.getAssets() |
|||
, "fonts/fontawesome-webfont.ttf")); |
|||
if (download.hasSubCategory()) { |
|||
String tmp = context.getResources().getString(R.string.fa_folder) + " " |
|||
+ download.getTitle(); |
|||
downloadViewHolder.title.setText(tmp); |
|||
} else { |
|||
String tmp = context.getResources().getString(R.string.fa_file) + " " |
|||
+ download.getTitle(); |
|||
downloadViewHolder.title.setText(tmp); |
|||
} |
|||
} else { |
|||
//TODO implement download on click
|
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
downloadViewHolder.upperLinear.setBackgroundColor(context.getResources().getColor(R.color.background, null)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
downloadViewHolder.upperLinear.setBackgroundColor(context.getResources().getColor(R.color.background)); |
|||
} |
|||
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE); |
|||
downloadViewHolder.informationExpandableBtn.setVisibility(View.GONE); |
|||
downloadViewHolder.informationExpandableBtn.setEnabled(false); |
|||
downloadViewHolder.title.setText(download.getTitle()); |
|||
} |
|||
|
|||
downloadViewHolder.subTitle.setText(download.getSubTitle()); |
|||
String tmp = download.getExtraInfo(); |
|||
if (tmp != null && !Objects.equals(tmp, "")) |
|||
downloadViewHolder.extraInfo.setText(tmp); |
|||
else downloadViewHolder.extraInfo.setVisibility(View.GONE); |
|||
tmp = download.getStatNumbers(); |
|||
if (tmp != null && !Objects.equals(tmp, "")) |
|||
downloadViewHolder.uploaderDate.setText(tmp); |
|||
else downloadViewHolder.uploaderDate.setVisibility(View.GONE); |
|||
} else if (holder instanceof LoadingViewHolder) { |
|||
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; |
|||
loadingViewHolder.progressBar.setIndeterminate(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return parsedDownloads.size(); |
|||
} |
|||
|
|||
private static class DownloadViewHolder extends RecyclerView.ViewHolder { |
|||
final LinearLayout upperLinear, downloadRow, informationExpandable; |
|||
final TextView title, subTitle, extraInfo, uploaderDate; |
|||
final ImageButton informationExpandableBtn; |
|||
|
|||
DownloadViewHolder(View download) { |
|||
super(download); |
|||
upperLinear = (LinearLayout) download.findViewById(R.id.upper_linear); |
|||
downloadRow = (LinearLayout) download.findViewById(R.id.download_row); |
|||
informationExpandable = (LinearLayout) download.findViewById(R.id.child_board_expandable); |
|||
title = (TextView) download.findViewById(R.id.download_title); |
|||
subTitle = (TextView) download.findViewById(R.id.download_sub_title); |
|||
extraInfo = (TextView) download.findViewById(R.id.download_extra_info); |
|||
uploaderDate = (TextView) download.findViewById(R.id.download_uploader_date); |
|||
informationExpandableBtn = (ImageButton) download.findViewById(R.id.download_information_button); |
|||
} |
|||
} |
|||
|
|||
private static class LoadingViewHolder extends RecyclerView.ViewHolder { |
|||
final MaterialProgressBar progressBar; |
|||
|
|||
LoadingViewHolder(View itemView) { |
|||
super(itemView); |
|||
progressBar = (MaterialProgressBar) itemView.findViewById(R.id.recycler_progress_bar); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,565 @@ |
|||
package gr.thmmy.mthmmy.base; |
|||
|
|||
import android.Manifest; |
|||
import android.app.ProgressDialog; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.content.pm.PackageManager; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v4.content.ContextCompat; |
|||
import android.support.v7.app.AppCompatActivity; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ImageButton; |
|||
import android.widget.Toast; |
|||
|
|||
import com.mikepenz.fontawesome_typeface_library.FontAwesome; |
|||
import com.mikepenz.iconics.IconicsDrawable; |
|||
import com.mikepenz.materialdrawer.AccountHeader; |
|||
import com.mikepenz.materialdrawer.AccountHeaderBuilder; |
|||
import com.mikepenz.materialdrawer.Drawer; |
|||
import com.mikepenz.materialdrawer.DrawerBuilder; |
|||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IProfile; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.AboutActivity; |
|||
import gr.thmmy.mthmmy.activities.BookmarkActivity; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity; |
|||
import gr.thmmy.mthmmy.activities.main.MainActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.model.Bookmark; |
|||
import gr.thmmy.mthmmy.model.ThmmyFile; |
|||
import gr.thmmy.mthmmy.services.DownloadService; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import okhttp3.OkHttpClient; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; |
|||
|
|||
public abstract class BaseActivity extends AppCompatActivity { |
|||
// Client & Cookies
|
|||
protected static OkHttpClient client; |
|||
|
|||
//SessionManager
|
|||
protected static SessionManager sessionManager; |
|||
|
|||
//Bookmarks
|
|||
private static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs"; |
|||
private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey"; |
|||
private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey"; |
|||
protected Bookmark thisPageBookmark; |
|||
protected ImageButton thisPageBookmarkButton; |
|||
private SharedPreferences bookmarksFile; |
|||
private ArrayList<Bookmark> topicsBookmarked; |
|||
private ArrayList<Bookmark> boardsBookmarked; |
|||
private static Drawable bookmarked; |
|||
private static Drawable notBookmarked; |
|||
|
|||
//Common UI elements
|
|||
protected Toolbar toolbar; |
|||
protected Drawer drawer; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
if (client == null) |
|||
client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
|
|||
// they become null when app restarts after crash
|
|||
if (sessionManager == null) |
|||
sessionManager = BaseApplication.getInstance().getSessionManager(); |
|||
|
|||
|
|||
if (sessionManager.isLoggedIn()) { |
|||
if (bookmarked == null) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true, null); |
|||
} else //noinspection deprecation
|
|||
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true); |
|||
} |
|||
if (notBookmarked == null) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false, null); |
|||
} else //noinspection deprecation
|
|||
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false); |
|||
} |
|||
if (topicsBookmarked == null || boardsBookmarked == null) { |
|||
bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE); |
|||
loadSavedBookmarks(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
super.onResume(); |
|||
updateDrawer(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPause() { |
|||
super.onPause(); |
|||
if (drawer != null) //close drawer animation after returning to activity
|
|||
drawer.closeDrawer(); |
|||
} |
|||
|
|||
|
|||
public static OkHttpClient getClient() { |
|||
return client; |
|||
} |
|||
|
|||
public static SessionManager getSessionManager() { |
|||
return sessionManager; |
|||
} |
|||
|
|||
//TODO: move stuff below (?)
|
|||
//------------------------------------------DRAWER STUFF----------------------------------------
|
|||
protected static final int HOME_ID = 0; |
|||
protected static final int DOWNLOADS_ID = 1; |
|||
protected static final int BOOKMARKS_ID = 2; |
|||
protected static final int LOG_ID = 3; |
|||
protected static final int ABOUT_ID = 4; |
|||
|
|||
private AccountHeader accountHeader; |
|||
private ProfileDrawerItem profileDrawerItem; |
|||
private PrimaryDrawerItem homeItem, downloadsItem, bookmarksItem, loginLogoutItem, aboutItem; |
|||
private IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected, |
|||
bookmarksIcon, bookmarksIconSelected, loginIcon, logoutIcon, aboutIcon, |
|||
aboutIconSelected; |
|||
|
|||
/** |
|||
* Call only after initializing Toolbar |
|||
*/ |
|||
protected void createDrawer() { |
|||
final int primaryColor = ContextCompat.getColor(this, R.color.iron); |
|||
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark); |
|||
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent); |
|||
|
|||
//Drawer Icons
|
|||
homeIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(primaryColor); |
|||
|
|||
homeIconSelected = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
downloadsIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_download) |
|||
.color(primaryColor); |
|||
|
|||
downloadsIconSelected = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_download) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
bookmarksIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_bookmark) |
|||
.color(primaryColor); |
|||
|
|||
bookmarksIconSelected = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_bookmark) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
loginIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_in) |
|||
.color(primaryColor); |
|||
|
|||
logoutIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_out) |
|||
.color(primaryColor); |
|||
|
|||
aboutIcon = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(primaryColor); |
|||
|
|||
aboutIconSelected = new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
//Drawer Items
|
|||
homeItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(HOME_ID) |
|||
.withName(R.string.home) |
|||
.withIcon(homeIcon) |
|||
.withSelectedIcon(homeIconSelected); |
|||
|
|||
|
|||
if (sessionManager.isLoggedIn()) //When logged in
|
|||
{ |
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID) |
|||
.withName(R.string.logout) |
|||
.withIcon(logoutIcon) |
|||
.withSelectable(false); |
|||
downloadsItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(DOWNLOADS_ID) |
|||
.withName(R.string.downloads) |
|||
.withIcon(downloadsIcon) |
|||
.withSelectedIcon(downloadsIconSelected); |
|||
bookmarksItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(BOOKMARKS_ID) |
|||
.withName(R.string.bookmark) |
|||
.withIcon(bookmarksIcon) |
|||
.withSelectedIcon(bookmarksIconSelected); |
|||
} else |
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID).withName(R.string.login) |
|||
.withIcon(loginIcon) |
|||
.withSelectable(false); |
|||
|
|||
aboutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(ABOUT_ID) |
|||
.withName(R.string.about) |
|||
.withIcon(aboutIcon) |
|||
.withSelectedIcon(aboutIconSelected); |
|||
|
|||
//Profile
|
|||
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername()); |
|||
|
|||
//AccountHeader
|
|||
accountHeader = new AccountHeaderBuilder() |
|||
.withActivity(this) |
|||
.withCompactStyle(true) |
|||
.withSelectionListEnabledForSingleProfile(false) |
|||
.withHeaderBackground(R.color.primary) |
|||
.addProfiles(profileDrawerItem) |
|||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { |
|||
@Override |
|||
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { |
|||
if (sessionManager.isLoggedIn()) { |
|||
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile"); |
|||
if (!sessionManager.hasAvatar()) |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); |
|||
else |
|||
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, sessionManager.getAvatarLink()); |
|||
extras.putString(BUNDLE_PROFILE_USERNAME, sessionManager.getUsername()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
startActivity(intent); |
|||
return false; |
|||
} |
|||
return true; |
|||
|
|||
} |
|||
}) |
|||
.build(); |
|||
|
|||
//Drawer
|
|||
DrawerBuilder drawerBuilder = new DrawerBuilder() |
|||
.withActivity(this) |
|||
.withToolbar(toolbar) |
|||
.withDrawerWidthDp((int) BaseApplication.getInstance().getDpWidth() / 2) |
|||
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.withAccountHeader(accountHeader) |
|||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { |
|||
@Override |
|||
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { |
|||
if (drawerItem.equals(HOME_ID)) { |
|||
if (!(BaseActivity.this instanceof MainActivity)) { |
|||
Intent i = new Intent(BaseActivity.this, MainActivity.class); |
|||
startActivity(i); |
|||
} |
|||
} else if (drawerItem.equals(DOWNLOADS_ID)) { |
|||
if (!(BaseActivity.this instanceof DownloadsActivity)) { |
|||
Intent i = new Intent(BaseActivity.this, DownloadsActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_DOWNLOADS_URL, ""); |
|||
extras.putString(BUNDLE_DOWNLOADS_TITLE, null); |
|||
i.putExtras(extras); |
|||
startActivity(i); |
|||
} |
|||
} else if (drawerItem.equals(BOOKMARKS_ID)) { |
|||
if (!(BaseActivity.this instanceof BookmarkActivity)) { |
|||
Intent i = new Intent(BaseActivity.this, BookmarkActivity.class); |
|||
startActivity(i); |
|||
} |
|||
} else if (drawerItem.equals(LOG_ID)) { |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
Intent intent = new Intent(BaseActivity.this, LoginActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); |
|||
} else |
|||
new LogoutTask().execute(); |
|||
} else if (drawerItem.equals(ABOUT_ID)) { |
|||
if (!(BaseActivity.this instanceof AboutActivity)) { |
|||
Intent i = new Intent(BaseActivity.this, AboutActivity.class); |
|||
startActivity(i); |
|||
} |
|||
|
|||
} |
|||
|
|||
drawer.closeDrawer(); |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
if (sessionManager.isLoggedIn()) |
|||
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, loginLogoutItem, aboutItem); |
|||
else |
|||
drawerBuilder.addDrawerItems(homeItem, loginLogoutItem, aboutItem); |
|||
|
|||
drawer = drawerBuilder.build(); |
|||
|
|||
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false); |
|||
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() { |
|||
@Override |
|||
public boolean onNavigationClickListener(View clickedView) { |
|||
onBackPressed(); |
|||
return true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected void updateDrawer() { |
|||
if (drawer != null) { |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
drawer.removeItem(DOWNLOADS_ID); |
|||
drawer.removeItem(BOOKMARKS_ID); |
|||
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(this, R.color.primary))); |
|||
} else { |
|||
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(sessionManager.getAvatarLink()); |
|||
} |
|||
accountHeader.updateProfile(profileDrawerItem); |
|||
drawer.updateItem(loginLogoutItem); |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
//-------------------------------------------LOGOUT-------------------------------------------------
|
|||
|
|||
/** |
|||
* Result toast will always display a success, because when user chooses logout all data are |
|||
* cleared regardless of the actual outcome |
|||
*/ |
|||
protected class LogoutTask extends AsyncTask<Void, Void, Integer> { //Attempt logout
|
|||
ProgressDialog progressDialog; |
|||
|
|||
protected Integer doInBackground(Void... voids) { |
|||
return sessionManager.logout(); |
|||
} |
|||
|
|||
protected void onPreExecute() { //Show a progress dialog until done
|
|||
progressDialog = new ProgressDialog(BaseActivity.this, |
|||
R.style.AppTheme_Dark_Dialog); |
|||
progressDialog.setCancelable(false); |
|||
progressDialog.setIndeterminate(true); |
|||
progressDialog.setMessage("Logging out..."); |
|||
progressDialog.show(); |
|||
} |
|||
|
|||
protected void onPostExecute(Integer result) { |
|||
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show(); |
|||
updateDrawer(); |
|||
progressDialog.dismiss(); |
|||
} |
|||
} |
|||
//-----------------------------------------LOGOUT END-----------------------------------------------
|
|||
|
|||
//---------------------------------------------BOOKMARKS--------------------------------------------
|
|||
|
|||
protected ArrayList<Bookmark> getBoardsBookmarked() { |
|||
return boardsBookmarked; |
|||
} |
|||
|
|||
protected ArrayList<Bookmark> getTopicsBookmarked() { |
|||
return topicsBookmarked; |
|||
} |
|||
|
|||
protected void setTopicBookmark() { |
|||
if (thisPageBookmark.matchExists(topicsBookmarked)) { |
|||
thisPageBookmarkButton.setImageDrawable(bookmarked); |
|||
} else { |
|||
thisPageBookmarkButton.setImageDrawable(notBookmarked); |
|||
} |
|||
thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
if (thisPageBookmark.matchExists(topicsBookmarked)) { |
|||
thisPageBookmarkButton.setImageDrawable(notBookmarked); |
|||
toggleTopicToBookmarks(thisPageBookmark); |
|||
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); |
|||
} else { |
|||
thisPageBookmarkButton.setImageDrawable(bookmarked); |
|||
toggleTopicToBookmarks(thisPageBookmark); |
|||
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected void setBoardBookmark() { |
|||
if (thisPageBookmark.matchExists(boardsBookmarked)) { |
|||
thisPageBookmarkButton.setImageDrawable(bookmarked); |
|||
} else { |
|||
thisPageBookmarkButton.setImageDrawable(notBookmarked); |
|||
} |
|||
thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
if (thisPageBookmark.matchExists(boardsBookmarked)) { |
|||
thisPageBookmarkButton.setImageDrawable(notBookmarked); |
|||
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); |
|||
} else { |
|||
thisPageBookmarkButton.setImageDrawable(bookmarked); |
|||
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); |
|||
} |
|||
toggleBoardToBookmarks(thisPageBookmark); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void loadSavedBookmarks() { |
|||
String tmpString = bookmarksFile.getString(BOOKMARKED_TOPICS_KEY, null); |
|||
if (tmpString != null) |
|||
topicsBookmarked = Bookmark.arrayFromString(tmpString); |
|||
else { |
|||
topicsBookmarked = new ArrayList<>(); |
|||
} |
|||
|
|||
tmpString = bookmarksFile.getString(BOOKMARKED_BOARDS_KEY, null); |
|||
if (tmpString != null) |
|||
boardsBookmarked = Bookmark.arrayFromString(tmpString); |
|||
else { |
|||
boardsBookmarked = new ArrayList<>(); |
|||
} |
|||
} |
|||
|
|||
private void toggleBoardToBookmarks(Bookmark bookmark) { |
|||
if (boardsBookmarked == null) return; |
|||
if (bookmark.matchExists(boardsBookmarked)) { |
|||
boardsBookmarked.remove(bookmark.findIndex(boardsBookmarked)); |
|||
} else boardsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId())); |
|||
updateBoardBookmarks(); |
|||
} |
|||
|
|||
private void toggleTopicToBookmarks(Bookmark bookmark) { |
|||
if (topicsBookmarked == null) return; |
|||
if (bookmark.matchExists(topicsBookmarked)) { |
|||
topicsBookmarked.remove(bookmark.findIndex(topicsBookmarked)); |
|||
} else { |
|||
topicsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId())); |
|||
} |
|||
updateTopicBookmarks(); |
|||
} |
|||
|
|||
private void updateBoardBookmarks() { |
|||
String tmpString; |
|||
tmpString = Bookmark.arrayToString(boardsBookmarked); |
|||
SharedPreferences.Editor editor = bookmarksFile.edit(); |
|||
editor.putString(BOOKMARKED_BOARDS_KEY, tmpString).apply(); |
|||
} |
|||
|
|||
private void updateTopicBookmarks() { |
|||
String tmpString; |
|||
tmpString = Bookmark.arrayToString(topicsBookmarked); |
|||
SharedPreferences.Editor editor = bookmarksFile.edit(); |
|||
editor.putString(BOOKMARKED_TOPICS_KEY, tmpString).apply(); |
|||
} |
|||
|
|||
protected void removeBookmark(Bookmark bookmark) { |
|||
if (bookmark.matchExists(boardsBookmarked)) toggleBoardToBookmarks(bookmark); |
|||
else if (bookmark.matchExists(topicsBookmarked)) toggleTopicToBookmarks(bookmark); |
|||
} |
|||
//-------------------------------------------BOOKMARKS END------------------------------------------
|
|||
|
|||
//-------PERMS---------
|
|||
private static final int PERMISSIONS_REQUEST_CODE = 69; |
|||
|
|||
//True if permissions are OK
|
|||
private boolean checkPerms() { |
|||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { |
|||
String[] PERMISSIONS_STORAGE = { |
|||
Manifest.permission.READ_EXTERNAL_STORAGE, |
|||
Manifest.permission.WRITE_EXTERNAL_STORAGE}; |
|||
|
|||
return !(checkSelfPermission(PERMISSIONS_STORAGE[0]) == PackageManager.PERMISSION_DENIED || |
|||
checkSelfPermission(PERMISSIONS_STORAGE[1]) == PackageManager.PERMISSION_DENIED); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
//Display popup gor user to grant permission
|
|||
public void requestPerms() { //Runtime permissions request for devices with API >= 23
|
|||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { |
|||
String[] PERMISSIONS_STORAGE = { |
|||
Manifest.permission.READ_EXTERNAL_STORAGE, |
|||
Manifest.permission.WRITE_EXTERNAL_STORAGE}; |
|||
|
|||
requestPermissions(PERMISSIONS_STORAGE, PERMISSIONS_REQUEST_CODE); |
|||
} |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions |
|||
, @NonNull int[] grantResults) { |
|||
switch (permsRequestCode) { |
|||
case PERMISSIONS_REQUEST_CODE: |
|||
launchDownloadService(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
|
|||
//----------------------------------DOWNLOAD----------------------
|
|||
private ThmmyFile tempThmmyFile; |
|||
|
|||
public void launchDownloadService(ThmmyFile thmmyFile) { |
|||
if (checkPerms()) |
|||
DownloadService.startActionDownload(this, thmmyFile.getFileUrl().toString()); |
|||
else { |
|||
tempThmmyFile = thmmyFile; |
|||
requestPerms(); |
|||
} |
|||
} |
|||
|
|||
//Uses temp file - called after permission grant
|
|||
public void launchDownloadService() { |
|||
if (checkPerms()) |
|||
DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString()); |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,111 @@ |
|||
package gr.thmmy.mthmmy.base; |
|||
|
|||
import android.app.Application; |
|||
import android.content.Context; |
|||
import android.content.SharedPreferences; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.support.v4.content.ContextCompat; |
|||
import android.util.DisplayMetrics; |
|||
import android.widget.ImageView; |
|||
|
|||
import com.franmontiel.persistentcookiejar.PersistentCookieJar; |
|||
import com.franmontiel.persistentcookiejar.cache.SetCookieCache; |
|||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; |
|||
import com.jakewharton.picasso.OkHttp3Downloader; |
|||
import com.mikepenz.fontawesome_typeface_library.FontAwesome; |
|||
import com.mikepenz.iconics.IconicsDrawable; |
|||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader; |
|||
import com.mikepenz.materialdrawer.util.DrawerImageLoader; |
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import okhttp3.OkHttpClient; |
|||
|
|||
public class BaseApplication extends Application { |
|||
|
|||
|
|||
private static BaseApplication baseApplication; //BaseApplication singleton
|
|||
|
|||
// Client & SessionManager
|
|||
private OkHttpClient client; |
|||
private SessionManager sessionManager; |
|||
|
|||
//Shared Preferences
|
|||
private final String SHARED_PREFS_NAME = "ThmmySharedPrefs"; |
|||
|
|||
//Display Metrics
|
|||
private static float dpHeight, dpWidth; |
|||
|
|||
public static BaseApplication getInstance(){ |
|||
return baseApplication; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate() { |
|||
super.onCreate(); |
|||
baseApplication = this; //init singleton
|
|||
|
|||
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); |
|||
SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext()); |
|||
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); |
|||
client = new OkHttpClient.Builder() |
|||
.cookieJar(cookieJar) |
|||
.connectTimeout(30, TimeUnit.SECONDS) |
|||
.writeTimeout(30, TimeUnit.SECONDS) |
|||
.readTimeout(30, TimeUnit.SECONDS) |
|||
.build(); |
|||
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs); |
|||
Picasso picasso = new Picasso.Builder(getApplicationContext()) |
|||
.downloader(new OkHttp3Downloader(client)) |
|||
.build(); |
|||
|
|||
Picasso.setSingletonInstance(picasso); //All following Picasso (with Picasso.with(Context context) requests will use this Picasso object
|
|||
|
|||
//Initialize and create the image loader logic
|
|||
DrawerImageLoader.init(new AbstractDrawerImageLoader() { |
|||
@Override |
|||
public void set(ImageView imageView, Uri uri, Drawable placeholder) { |
|||
Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView); |
|||
} |
|||
@Override |
|||
public void cancel(ImageView imageView) { |
|||
Picasso.with(imageView.getContext()).cancelRequest(imageView); |
|||
} |
|||
|
|||
@Override |
|||
public Drawable placeholder(Context ctx, String tag) { |
|||
if (DrawerImageLoader.Tags.PROFILE.name().equals(tag)) { |
|||
return new IconicsDrawable(ctx).icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(ctx, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(ctx, R.color.primary)); |
|||
} |
|||
return super.placeholder(ctx, tag); |
|||
} |
|||
}); |
|||
|
|||
DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics(); |
|||
dpHeight = displayMetrics.heightPixels / displayMetrics.density; |
|||
dpWidth = displayMetrics.widthPixels / displayMetrics.density; |
|||
} |
|||
|
|||
public OkHttpClient getClient() { |
|||
return client; |
|||
} |
|||
|
|||
public SessionManager getSessionManager() { |
|||
return sessionManager; |
|||
} |
|||
|
|||
public float getDpHeight() { |
|||
return dpHeight; |
|||
} |
|||
|
|||
public float getDpWidth() { |
|||
return dpWidth; |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import android.support.annotation.NonNull; |
|||
import android.support.annotation.Nullable; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
public class Bookmark implements java.io.Serializable { |
|||
private final String title, id; |
|||
|
|||
public Bookmark(String title, String id) { |
|||
this.title = title; |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public boolean matchExists(ArrayList<Bookmark> array) { |
|||
if (array != null && !array.isEmpty()) { |
|||
for (Bookmark bookmark : array) { |
|||
if (bookmark != null) { |
|||
if (Objects.equals(bookmark.getId(), this.id) |
|||
&& Objects.equals(bookmark.getTitle(), this.title)) |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public int findIndex(ArrayList<Bookmark> array) { |
|||
if (array != null && !array.isEmpty()) { |
|||
for (int i = 0; i < array.size(); ++i) { |
|||
if (array.get(i) != null && Objects.equals(array.get(i).getId(), this.id) |
|||
&& Objects.equals(array.get(i).getTitle(), this.title)) |
|||
return i; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
@Nullable |
|||
public static String arrayToString(@NonNull ArrayList<Bookmark> arrayList) { |
|||
String returnString = ""; |
|||
for (Bookmark bookmark : arrayList) { |
|||
if (bookmark != null) { |
|||
returnString += (bookmark.getId() + "\t"); |
|||
returnString += (bookmark.getTitle() + "\n"); |
|||
} |
|||
} |
|||
if (!Objects.equals(returnString, "")) return returnString; |
|||
else return null; |
|||
} |
|||
|
|||
public static ArrayList<Bookmark> arrayFromString(@NonNull String string) { |
|||
ArrayList<Bookmark> returnArray = new ArrayList<>(); |
|||
String[] lines = string.split("\n"); |
|||
for (String line : lines) { |
|||
if (line == null || line.isEmpty() || Objects.equals(line, "")) break; |
|||
String[] parameters = line.split("\t"); |
|||
if (parameters.length != 2) break; |
|||
returnArray.add(new Bookmark(parameters[1], parameters[0])); |
|||
} |
|||
return returnArray; |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
public class Download { |
|||
public enum DownloadItemType {DOWNLOADS_CATEGORY, DOWNLOADS_FILE} |
|||
|
|||
private final String url, title, subTitle, statNumbers, extraInfo; |
|||
private final boolean hasSubCategory; |
|||
private final DownloadItemType type; |
|||
|
|||
public Download() { |
|||
type = null; |
|||
url = null; |
|||
title = null; |
|||
subTitle = null; |
|||
statNumbers = null; |
|||
hasSubCategory = false; |
|||
extraInfo = null; |
|||
} |
|||
|
|||
public Download(DownloadItemType type, String url, String title, String subTitle, |
|||
String statNumbers, boolean hasSubCategory, String extraInfo) { |
|||
this.type = type; |
|||
this.url = url; |
|||
this.title = title; |
|||
this.subTitle = subTitle; |
|||
this.statNumbers = statNumbers; |
|||
this.hasSubCategory = hasSubCategory; |
|||
this.extraInfo = extraInfo; |
|||
} |
|||
|
|||
public DownloadItemType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public String getUrl() { |
|||
return url; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public String getSubTitle() { |
|||
return subTitle; |
|||
} |
|||
|
|||
public String getStatNumbers() { |
|||
return statNumbers; |
|||
} |
|||
|
|||
public String getExtraInfo() { |
|||
return extraInfo; |
|||
} |
|||
|
|||
public boolean hasSubCategory() { |
|||
return hasSubCategory; |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import java.net.URL; |
|||
|
|||
public class ThmmyFile { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "ThmmyFile"; |
|||
private final URL fileUrl; |
|||
private final String filename, fileInfo; |
|||
|
|||
/** |
|||
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. |
|||
* |
|||
* @param fileUrl {@link URL} object with file's url |
|||
* @param filename {@link String} with desired file name |
|||
* @param fileInfo {@link String} with any extra information (like number of downloads) |
|||
*/ |
|||
public ThmmyFile(URL fileUrl, String filename, String fileInfo) { |
|||
this.fileUrl = fileUrl; |
|||
this.filename = filename; |
|||
this.fileInfo = fileInfo; |
|||
} |
|||
|
|||
public URL getFileUrl() { |
|||
return fileUrl; |
|||
} |
|||
|
|||
public String getFilename() { |
|||
return filename; |
|||
} |
|||
|
|||
public String getFileInfo() { |
|||
return fileInfo; |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
package gr.thmmy.mthmmy.receiver; |
|||
|
|||
import android.app.Notification; |
|||
import android.app.NotificationManager; |
|||
import android.app.PendingIntent; |
|||
import android.content.BroadcastReceiver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.net.Uri; |
|||
import android.os.Bundle; |
|||
import android.support.v7.app.NotificationCompat; |
|||
import android.webkit.MimeTypeMap; |
|||
|
|||
import java.io.File; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import mthmmy.utils.Report; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.COMPLETED; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_DOWNLOAD_ID; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_DOWNLOAD_STATE; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_FILE_EXTENSION; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_FILE_NAME; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TEXT; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TICKER; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TITLE; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.SAVE_DIR; |
|||
import static gr.thmmy.mthmmy.services.DownloadService.STARTED; |
|||
|
|||
public class Receiver extends BroadcastReceiver { |
|||
private static final String TAG = "BroadcastReceiver"; |
|||
|
|||
public Receiver() { |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Context context, Intent intent) { |
|||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context); |
|||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
|||
|
|||
if (intent.getAction().equals(ACTION_DOWNLOAD)) { |
|||
Bundle extras = intent.getExtras(); |
|||
int id = extras.getInt(EXTRA_DOWNLOAD_ID); |
|||
String state = extras.getString(EXTRA_DOWNLOAD_STATE, "NONE"); |
|||
String title = extras.getString(EXTRA_NOTIFICATION_TITLE); |
|||
String text = extras.getString(EXTRA_NOTIFICATION_TEXT); |
|||
String ticker = extras.getString(EXTRA_NOTIFICATION_TICKER); |
|||
|
|||
builder.setContentTitle(title) |
|||
.setContentText(text) |
|||
.setTicker(ticker) |
|||
.setAutoCancel(true) //???
|
|||
.setSmallIcon(R.mipmap.ic_launcher); |
|||
|
|||
if (state.equals(STARTED)) |
|||
builder.setOngoing(true); |
|||
else if (state.equals(COMPLETED)) { |
|||
String fileName = extras.getString(EXTRA_FILE_NAME, "NONE"); |
|||
String extension = extras.getString(EXTRA_FILE_EXTENSION, "extension"); |
|||
|
|||
File file = new File(SAVE_DIR, fileName); |
|||
if (file.exists()) { |
|||
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension( |
|||
MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath())); |
|||
|
|||
Intent chooser = new Intent(); |
|||
chooser.setAction(android.content.Intent.ACTION_VIEW); |
|||
chooser.setDataAndType(Uri.fromFile(file), type); |
|||
chooser.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
|||
|
|||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT); |
|||
builder.setContentIntent(pendingIntent); |
|||
} else |
|||
Report.w(TAG, "File doesn't exist."); |
|||
} |
|||
Notification notification = builder.build(); |
|||
notificationManager.notify(id, notification); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,221 @@ |
|||
package gr.thmmy.mthmmy.services; |
|||
|
|||
import android.app.IntentService; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.IntentFilter; |
|||
import android.os.Environment; |
|||
import android.support.annotation.NonNull; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileNotFoundException; |
|||
import java.io.IOException; |
|||
|
|||
import gr.thmmy.mthmmy.base.BaseApplication; |
|||
import gr.thmmy.mthmmy.receiver.Receiver; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import okio.BufferedSink; |
|||
import okio.Okio; |
|||
|
|||
/** |
|||
* An {@link IntentService} subclass for handling asynchronous task requests in |
|||
* a service on a separate handler thread. |
|||
*/ |
|||
public class DownloadService extends IntentService { |
|||
private static final String TAG = "DownloadService"; |
|||
private static int sDownloadId =0; |
|||
|
|||
private Receiver receiver; |
|||
|
|||
public static final String SAVE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mthmmy"; |
|||
|
|||
public static final String ACTION_DOWNLOAD = "gr.thmmy.mthmmy.services.action.DOWNLOAD"; |
|||
public static final String EXTRA_DOWNLOAD_URL = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_URL"; |
|||
|
|||
public static final String EXTRA_DOWNLOAD_ID = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_ID"; |
|||
public static final String EXTRA_DOWNLOAD_STATE = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_STATE"; |
|||
public static final String EXTRA_FILE_NAME = "gr.thmmy.mthmmy.services.extra.FILE_NAME"; |
|||
public static final String EXTRA_FILE_EXTENSION = "gr.thmmy.mthmmy.services.extra.FILE_EXTENSION"; |
|||
public static final String EXTRA_NOTIFICATION_TITLE = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TITLE"; |
|||
public static final String EXTRA_NOTIFICATION_TEXT = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TEXT"; |
|||
public static final String EXTRA_NOTIFICATION_TICKER = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TICKER"; |
|||
|
|||
public static final String STARTED = "Started"; |
|||
public static final String COMPLETED = "Completed"; |
|||
public static final String FAILED = "Failed"; |
|||
|
|||
|
|||
|
|||
public DownloadService() { |
|||
super("DownloadService"); |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate() { |
|||
super.onCreate(); |
|||
final IntentFilter filter = new IntentFilter(DownloadService.ACTION_DOWNLOAD); |
|||
receiver = new Receiver(); |
|||
registerReceiver(receiver, filter); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
this.unregisterReceiver(receiver); |
|||
} |
|||
|
|||
/** |
|||
* Starts this service to perform action Download with the given parameters. If |
|||
* the service is already performing a task this action will be queued. |
|||
* |
|||
* @see IntentService |
|||
*/ |
|||
public static void startActionDownload(Context context, String downloadUrl) { |
|||
Intent intent = new Intent(context, DownloadService.class); |
|||
intent.setAction(ACTION_DOWNLOAD); |
|||
intent.putExtra(EXTRA_DOWNLOAD_URL, downloadUrl); |
|||
context.startService(intent); |
|||
} |
|||
|
|||
@Override |
|||
protected void onHandleIntent(Intent intent) { |
|||
if (intent != null) { |
|||
final String action = intent.getAction(); |
|||
if (ACTION_DOWNLOAD.equals(action)) { |
|||
final String downloadLink = intent.getStringExtra(EXTRA_DOWNLOAD_URL); |
|||
handleActionDownload(downloadLink); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle action Foo in the provided background thread with the provided |
|||
* parameters. |
|||
*/ |
|||
private void handleActionDownload(String downloadLink) { |
|||
OkHttpClient client = BaseApplication.getInstance().getClient(); |
|||
BufferedSink sink = null; |
|||
String fileName = "file"; |
|||
String extension = "extension"; |
|||
int downloadId = sDownloadId; |
|||
sDownloadId++; |
|||
|
|||
try { |
|||
Request request = new Request.Builder().url(downloadLink).build(); |
|||
Response response = client.newCall(request).execute(); |
|||
|
|||
String contentType = response.headers("Content-Type").toString(); //check if link provides a binary file
|
|||
if(contentType.equals("[application/octet-stream]")) |
|||
{ |
|||
fileName = response.headers("Content-Disposition").toString().split("\"")[1]; |
|||
|
|||
File dirPath = new File(SAVE_DIR); |
|||
if(!dirPath.isDirectory()) |
|||
{ |
|||
if(dirPath.mkdirs()) |
|||
Report.i(TAG, "mTHMMY's directory created successfully!"); |
|||
else |
|||
Report.e(TAG, "Couldn't create mTHMMY's directory..."); |
|||
} |
|||
|
|||
|
|||
String nameFormat; |
|||
String[] tokens = fileName.split("\\.(?=[^\\.]+$)"); |
|||
|
|||
if(tokens.length!=2) |
|||
{ |
|||
Report.w(TAG, "Couldn't get file extension..."); |
|||
nameFormat = fileName + "(%d)"; |
|||
} |
|||
else |
|||
{ |
|||
nameFormat = tokens[0] + "(%d)." + tokens[1]; |
|||
extension = tokens[1]; |
|||
} |
|||
|
|||
|
|||
|
|||
File file = new File(dirPath, fileName); |
|||
|
|||
for (int i = 1;;i++) { |
|||
if (!file.exists()) { |
|||
break; |
|||
} |
|||
|
|||
file = new File(dirPath, String.format(nameFormat, i)); |
|||
} |
|||
|
|||
fileName = file.getName(); |
|||
|
|||
Report.v(TAG, "Started saving file " + fileName); |
|||
sendNotification(downloadId, STARTED, fileName, extension); |
|||
|
|||
sink = Okio.buffer(Okio.sink(file)); |
|||
sink.writeAll(response.body().source()); |
|||
sink.flush(); |
|||
Report.i(TAG, "Download OK!"); |
|||
sendNotification(downloadId, COMPLETED, fileName, extension); |
|||
} |
|||
else |
|||
Report.e(TAG, "Response not a binary file!"); |
|||
} |
|||
catch (FileNotFoundException e){ |
|||
Report.e(TAG, "FileNotFound", e); |
|||
Report.i(TAG, "Download failed..."); |
|||
sendNotification(downloadId, FAILED, fileName, extension); |
|||
} |
|||
catch (IOException e){ |
|||
Report.e(TAG, "IOException", e); |
|||
Report.i(TAG, "Download failed..."); |
|||
sendNotification(downloadId, FAILED, fileName, extension); |
|||
} finally { |
|||
if (sink!= null) { |
|||
try { |
|||
sink.close(); |
|||
} catch (IOException e) { |
|||
// Ignore - Significant errors should already have been reported
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void sendNotification(int downloadId, String type, @NonNull String fileName, String fileExtension) |
|||
{ |
|||
Intent intent = new Intent(ACTION_DOWNLOAD); |
|||
switch (type) { |
|||
case STARTED: { |
|||
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Started"); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" downloading..."); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Downloading..."); |
|||
break; |
|||
} |
|||
case COMPLETED: { |
|||
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Completed"); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" finished downloading."); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Completed"); |
|||
break; |
|||
} |
|||
case FAILED: { |
|||
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Failed"); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" failed."); |
|||
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Failed"); |
|||
break; |
|||
} |
|||
default:{ |
|||
Report.wtf(TAG, "Invalid notification case!"); |
|||
return; |
|||
} |
|||
} |
|||
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId); |
|||
intent.putExtra(EXTRA_DOWNLOAD_STATE, type); |
|||
intent.putExtra(EXTRA_FILE_NAME, fileName); |
|||
intent.putExtra(EXTRA_FILE_EXTENSION, fileExtension); |
|||
sendBroadcast(intent); |
|||
|
|||
} |
|||
|
|||
} |
@ -1,194 +0,0 @@ |
|||
package gr.thmmy.mthmmy.utils.FileManager; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Environment; |
|||
import android.os.StatFs; |
|||
import android.support.annotation.Nullable; |
|||
import android.webkit.MimeTypeMap; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileOutputStream; |
|||
import java.io.IOException; |
|||
import java.net.URL; |
|||
import java.util.Objects; |
|||
|
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.base.BaseActivity.getClient; |
|||
|
|||
/** |
|||
* Used for downloading and storing a file from the forum using {@link okhttp3}. |
|||
* <p>Class has one constructor, {@link #ThmmyFile(URL, String, String)}. |
|||
*/ |
|||
public class ThmmyFile { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "ThmmyFile"; |
|||
private final URL fileUrl; |
|||
private final String filename, fileInfo; |
|||
private String extension, filePath; |
|||
private File file; |
|||
|
|||
/** |
|||
* This constructor only creates a empty ThmmyFile object and <b>does not download</b> the file. To download |
|||
* the file use {@link #download(Context)} after setting file's url! |
|||
*/ |
|||
public ThmmyFile() { |
|||
this.fileUrl = null; |
|||
this.filename = null; |
|||
this.fileInfo = null; |
|||
this.extension = null; |
|||
this.filePath = null; |
|||
this.file = null; |
|||
} |
|||
|
|||
/** |
|||
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. To download |
|||
* the file use {@link #download(Context)}! |
|||
* |
|||
* @param fileUrl {@link URL} object with file's url |
|||
* @param filename {@link String} with desired file name |
|||
* @param fileInfo {@link String} with any extra information (like number of downloads) |
|||
*/ |
|||
public ThmmyFile(URL fileUrl, String filename, String fileInfo) { |
|||
this.fileUrl = fileUrl; |
|||
this.filename = filename; |
|||
this.fileInfo = fileInfo; |
|||
this.extension = null; |
|||
this.filePath = null; |
|||
this.file = null; |
|||
} |
|||
|
|||
public URL getFileUrl() { |
|||
return fileUrl; |
|||
} |
|||
|
|||
public String getFilename() { |
|||
return filename; |
|||
} |
|||
|
|||
public String getFileInfo() { |
|||
return fileInfo; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download(Context)} is called and has succeeded. |
|||
* |
|||
* @return String with file's extension or null |
|||
*/ |
|||
@Nullable |
|||
public String getExtension() { |
|||
return extension; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download(Context)} is called and has succeeded. |
|||
* |
|||
* @return String with file's path or null |
|||
*/ |
|||
@Nullable |
|||
public String getFilePath() { |
|||
return filePath; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download(Context)} is called and has succeeded. |
|||
* |
|||
* @return {@link File} or null |
|||
*/ |
|||
@Nullable |
|||
public File getFile() { |
|||
return file; |
|||
} |
|||
|
|||
private void setExtension(String extension) { |
|||
this.extension = extension; |
|||
} |
|||
|
|||
private void setFilePath(String filePath) { |
|||
this.filePath = filePath; |
|||
} |
|||
|
|||
/** |
|||
* Used to download the file. If download is successful file's extension and path will be assigned |
|||
* to object's fields and can be accessed using getter methods. |
|||
* <p>File is stored in sdcard1/Android/data/Downloads/packageName</p> |
|||
* |
|||
* @return the {@link File} if successful, null otherwise |
|||
* @throws IOException if the request could not be executed due to cancellation, a connectivity |
|||
* problem or timeout. Because networks can fail during an exchange, it is possible that the |
|||
* remote server accepted the request before the failure. |
|||
* @throws SecurityException if the requested file is not hosted by the forum. |
|||
*/ |
|||
@Nullable |
|||
public File download(Context context) throws IOException, SecurityException, OutOfMemoryError { |
|||
if (fileUrl == null) { |
|||
return null; |
|||
} |
|||
if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr")) |
|||
throw new SecurityException("Downloading files from other sources is not supported"); |
|||
|
|||
Request request = new Request.Builder().url(fileUrl).build(); |
|||
|
|||
Response response = getClient().newCall(request).execute(); |
|||
if (!response.isSuccessful()) { |
|||
throw new IOException("Failed to download file: " + response); |
|||
} |
|||
file = getOutputMediaFile(context, filename, fileInfo); |
|||
if (file == null) { |
|||
Report.d(TAG, "Error creating media file, check storage permissions!"); |
|||
} else { |
|||
FileOutputStream fos = new FileOutputStream(file); |
|||
fos.write(response.body().bytes()); |
|||
fos.close(); |
|||
|
|||
filePath = file.getAbsolutePath(); |
|||
extension = MimeTypeMap.getFileExtensionFromUrl( |
|||
filePath.substring(filePath.lastIndexOf("/"))); |
|||
} |
|||
return file; |
|||
} |
|||
|
|||
@Nullable |
|||
private File getOutputMediaFile(Context context, String fileName, String fileInfo) throws OutOfMemoryError, IOException { |
|||
File mediaStorageDir; |
|||
String extState = Environment.getExternalStorageState(); |
|||
if (Environment.isExternalStorageRemovable() && |
|||
Objects.equals(extState, Environment.MEDIA_MOUNTED)) { |
|||
mediaStorageDir = new File(Environment.getExternalStorageDirectory() |
|||
+ "/Android/data/gr.thmmy.mthmmy/" |
|||
+ "Downloads/"); |
|||
} else { |
|||
mediaStorageDir = new File(context.getFilesDir(), "Downloads"); |
|||
} |
|||
|
|||
//Creates the storage directory if it does not exist
|
|||
if (!mediaStorageDir.exists()) { |
|||
if (!mediaStorageDir.mkdirs()) { |
|||
Report.d(TAG, "problem!"); |
|||
throw new IOException("Error.\nCouldn't create the path!"); |
|||
} |
|||
} |
|||
|
|||
|
|||
if (fileInfo != null) { |
|||
if (fileInfo.contains("KB")) { |
|||
float fileSize = Float.parseFloat(fileInfo |
|||
.substring(fileInfo.indexOf("(") + 1, fileInfo.indexOf("KB") - 1)); |
|||
|
|||
StatFs stat = new StatFs(mediaStorageDir.getPath()); |
|||
long bytesAvailable = stat.getBlockSizeLong() * stat.getAvailableBlocksLong(); |
|||
if ((bytesAvailable / 1024.f) < fileSize) |
|||
throw new OutOfMemoryError("There is not enough memory!"); |
|||
} |
|||
} |
|||
|
|||
//Creates a media file name
|
|||
File mediaFile; |
|||
mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName); |
|||
return mediaFile; |
|||
} |
|||
} |
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 392 B |
After Width: | Height: | Size: 203 B |
After Width: | Height: | Size: 161 B |
Before Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 270 B |
After Width: | Height: | Size: 336 B |
After Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 266 B |
After Width: | Height: | Size: 509 B |
After Width: | Height: | Size: 478 B |
After Width: | Height: | Size: 362 B |
Before Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 746 B |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 408 B |
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|||
<vector |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportHeight="24.0" |
|||
android:viewportWidth="24.0"> |
|||
<path |
|||
android:fillColor="#FFFFFF" |
|||
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/> |
|||
</vector> |
@ -0,0 +1,57 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<android.support.design.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:id="@+id/main_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.topic.TopicActivity"> |
|||
|
|||
<android.support.design.widget.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"> |
|||
|
|||
<android.support.v7.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
app:popupTheme="@style/ToolbarTheme"> |
|||
</android.support.v7.widget.Toolbar> |
|||
</android.support.design.widget.AppBarLayout> |
|||
|
|||
<android.support.v4.widget.NestedScrollView |
|||
android:id="@+id/bookmarks_nested_scroll" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="top|start" |
|||
android:layout_marginTop="64dp" |
|||
android:background="@color/background" |
|||
android:scrollbars="none"> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/bookmarks_container" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical"/> |
|||
</android.support.v4.widget.NestedScrollView> |
|||
|
|||
<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"/> |
|||
</android.support.design.widget.CoordinatorLayout> |
|||
|
|||
|
@ -0,0 +1,33 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:id="@+id/bookmark_row" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/selectableItemBackground" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
android:gravity="center_vertical" |
|||
android:orientation="horizontal" |
|||
android:paddingBottom="6dp" |
|||
android:paddingEnd="16dp" |
|||
android:paddingStart="16dp" |
|||
android:paddingTop="6dp"> |
|||
|
|||
<TextView |
|||
android:id="@+id/bookmark_title" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:paddingBottom="2dp" |
|||
android:textColor="@color/primary_text" |
|||
android:textSize="18sp"/> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/remove_bookmark" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/selectableItemBackground" |
|||
android:contentDescription="@string/remove_bookmark" |
|||
android:src="@drawable/ic_remove_circle"/> |
|||
</LinearLayout> |
@ -0,0 +1,62 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<android.support.design.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:id="@+id/main_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.downloads.DownloadsActivity"> |
|||
|
|||
<android.support.design.widget.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"> |
|||
|
|||
<android.support.v7.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
app:popupTheme="@style/ToolbarTheme"> |
|||
</android.support.v7.widget.Toolbar> |
|||
</android.support.design.widget.AppBarLayout> |
|||
|
|||
<android.support.v7.widget.RecyclerView |
|||
android:id="@+id/downloads_recycler_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="top|start" |
|||
android:layout_marginTop="64dp" |
|||
android:background="@color/background" |
|||
android:scrollbars="none" |
|||
tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity"> |
|||
</android.support.v7.widget.RecyclerView> |
|||
|
|||
<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"/> |
|||
|
|||
<android.support.design.widget.FloatingActionButton |
|||
android:id="@+id/download_fab" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="bottom|end" |
|||
android:layout_marginBottom="@dimen/fab_margins" |
|||
android:layout_marginEnd="@dimen/fab_margins" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" |
|||
app:srcCompat="@drawable/ic_file_upload"/> |
|||
</android.support.design.widget.CoordinatorLayout> |
|||
|
|||
|
@ -0,0 +1,76 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:id="@+id/upper_linear" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/card_background" |
|||
android:orientation="vertical"> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/download_row" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/selectableItemBackground" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
android:orientation="vertical" |
|||
android:paddingEnd="16dp" |
|||
android:paddingStart="16dp"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:layout_marginTop="5dp" |
|||
android:orientation="horizontal"> |
|||
|
|||
<TextView |
|||
android:id="@+id/download_title" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center_vertical" |
|||
android:layout_marginBottom="2dp" |
|||
android:layout_marginTop="2dp" |
|||
android:layout_weight="1" |
|||
android:paddingBottom="2dp" |
|||
android:textColor="@color/accent" |
|||
android:textSize="18sp"/> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/download_information_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:background="@null" |
|||
android:contentDescription="@string/child_board_button" |
|||
android:src="@drawable/ic_arrow_drop_down"/> |
|||
</LinearLayout> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/child_board_expandable" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:orientation="vertical" |
|||
android:visibility="gone"> |
|||
|
|||
<TextView |
|||
android:id="@+id/download_sub_title" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/secondary_text"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/download_extra_info" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/secondary_text"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/download_uploader_date" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/secondary_text"/> |
|||
</LinearLayout> |
|||
</LinearLayout> |
|||
</LinearLayout> |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<paths> |
|||
<external-path |
|||
name="my_downloads" |
|||
path="Downloads/"/> |
|||
|
|||
<files-path |
|||
name="my_downloads" |
|||
path="Downloads/"/> |
|||
</paths> |
@ -0,0 +1,34 @@ |
|||
[center][size=25pt][b]Introduction[/b][/size][/center] |
|||
|
|||
Από τη συζήτηση [url=https://www.thmmy.gr/smf/index.php?topic=67629.0]εδώ[/url], ξεκίνησε ένα project με στόχο τη δημιουργία εφαρμογής για Android κινητά που θα συγκεντρώνει και θα κάνει πιο εύκολη τη πρόσβαση σε μερικές από τις βασικές σελίδες και υπηρεσίες που αφορούν τη σχολή και χρησιμοποιούμε καθημερινά. |
|||
|
|||
Μετά από 2+ μήνες δουλειάς και πάνω από 9000 γραμμές κώδικα [size=9pt](.java, .xml και άλλα)[/size], σήμερα ανεβάσαμε για πρώτη φορά την εφαρμογή (σε closed alpha phase) στο Google Play Store! |
|||
|
|||
Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν. |
|||
|
|||
Αυτή τη στιγμή με το project ασχολούμαστε εγώ και ο L, ενώ έχουν βοηθήσει ο iason1907 και ο [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url]. |
|||
|
|||
[hr] |
|||
[center][size=25pt][b]Κάλεσμα για contributors[/b][/size] |
|||
|
|||
[img height=400]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center] |
|||
|
|||
Αν ενδιαφέρεσαι κι ΕΣΥ να ασχοληθείς με το project μπορείς να το κάνεις με πολλούς τρόπους: |
|||
[list] |
|||
[li]Αν ξέρεις προγραμματισμό μπορείς αρχικά να ζητήσεις πρόσβαση στο repository. Χρειάζονται [i]άμεσα[/i] νέοι developers για υλοποιήση καινούργιων χαρακτηριστικών, διόρθωση εντόμων, σύνταξη των javadocs και του documentation γενικότερα, white-box testing, υλοποίηση του backend στο server που στήθηκε πρόσφατα και πολλών άλλων. Αυτό, αρχικά, θα γίνεται με forks και merge requests - και πάντα στο ρυθμό που μπορείς να συνεισφέρεις. |
|||
[/li] |
|||
[li] |
|||
Να αναφέρεις bugs, να προτείνεις βελτιώσεις και να συμμετέχεις σε συζητήσεις, είτε στον [url=https://discord.gg/PVRVjth]Discord server[/url], είτε αποκτώντας πρόσβαση στον Issue Tracker μας. |
|||
[/li] |
|||
[li] |
|||
Να κατεβάσεις και να δοκιμάσεις την alpha έκδοση της εφαρμογής από [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy]εδώ[/url] αφού πρώτα μας δώσεις ένα gmail για να αποκτήσεις πρόσβαση. |
|||
[/li] |
|||
[li] |
|||
Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του Discord ή στέλνοντας email στο thmmynolife@gmail.com. |
|||
[/li][/list] |
|||
[hr] |
|||
[center][size=25pt][b]Η εφαρμογή[/b][/size][/center] |
|||
|
|||
[center][url=https://s6.postimg.org/4mupem3jl/image.png][img width=200]https://s6.postimg.org/4mupem3jl/image.png[/img][/url] [url=https://s6.postimg.org/ovdhmldgx/image.png][img width=200]https://s6.postimg.org/ovdhmldgx/image.png[/img][/url] [url=https://s6.postimg.org/a9mgycgoh/image.png][img width=200]https://s6.postimg.org/a9mgycgoh/image.png[/img][/url] [url=https://s6.postimg.org/5jwj9qpo1/image.png][img width=200]https://s6.postimg.org/5jwj9qpo1/image.png[/img][/url] [url=https://s6.postimg.org/fjrfpn0xd/image.png][img width=200]https://s6.postimg.org/fjrfpn0xd/image.png[/img][/url] [url=https://s6.postimg.org/z0c5c5w1d/image.png][img width=200]https://s6.postimg.org/z0c5c5w1d/image.png[/img][/url][/center] |
|||
|
|||
Αυτή τη στιγμή στην εφαρμογή μπορείς να κάνεις login και logout, να δεις τα "Πρόσφατα", να περιηγηθείς στα boards, topics και user profiles και να κατεβάσεις αρχεία συνημμένα σε post. |